Require HTTPS for OIDC and public URLs; stop logging token responses
Address review findings on a10b24c:
- F1: the OIDC issuer and the discovered authorization, token and JWKS
endpoints must be https://; plain http:// is allowed only for the
loopback hosts localhost, 127.0.0.1 and [::1]. Config rejects a remote
http:// issuer at startup; auth.New rejects a discovered remote http://
endpoint, naming it, before it is used.
- F2: a failed code exchange is reduced to a fixed category plus the HTTP
status; the token endpoint's body and error_description are never logged
or wrapped into the returned error.
- F3: MEALQUEST_PUBLIC_URL must be https:// unless its host is loopback,
so the Secure login cookie is always sent back on the callback.
The rule lives once, in internal/secureurl.Check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Package secureurl holds the one transport rule for URLs that carry
|
||||
// credentials or session cookies: HTTPS, or plain HTTP to a loopback host for
|
||||
// local development and tests.
|
||||
package secureurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// loopbackHosts are the only hosts plain HTTP is allowed for, compared
|
||||
// case-insensitively. url.URL's Hostname strips the brackets from "[::1]".
|
||||
var loopbackHosts = map[string]bool{"localhost": true, "127.0.0.1": true, "::1": true}
|
||||
|
||||
// Check reports an error unless u is an absolute https:// URL, or an http://
|
||||
// URL whose host is localhost, 127.0.0.1 or ::1.
|
||||
func Check(u *url.URL) error {
|
||||
switch {
|
||||
case u.Hostname() == "":
|
||||
return errors.New("URL has no host")
|
||||
case u.Scheme == "https":
|
||||
return nil
|
||||
case u.Scheme == "http" && loopbackHosts[strings.ToLower(u.Hostname())]:
|
||||
return nil
|
||||
case u.Scheme == "http":
|
||||
return errors.New("plain http:// is allowed only for localhost, 127.0.0.1 or [::1]; use https://")
|
||||
default:
|
||||
return fmt.Errorf("unsupported scheme %q (want https://)", u.Scheme)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package secureurl
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
raw string
|
||||
ok bool
|
||||
}{
|
||||
{"https://meals.example.com", true},
|
||||
{"https://auth.example.com/application/o/mealquest/", true},
|
||||
{"https://192.0.2.10:8443", true},
|
||||
{"http://localhost:8080", true},
|
||||
{"http://LOCALHOST:8080", true},
|
||||
{"http://127.0.0.1:8080/path", true},
|
||||
{"http://[::1]:8080", true},
|
||||
{"http://[::1]", true},
|
||||
{"http://meals.example.com", false},
|
||||
{"http://192.0.2.10", false},
|
||||
{"http://127.0.0.2", false},
|
||||
{"http://localhost.example.com", false},
|
||||
{"http://0.0.0.0:8080", false},
|
||||
{"ftp://localhost", false},
|
||||
{"https://", false},
|
||||
{"meals.example.com", false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
u, err := url.Parse(tc.raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Check(u); (err == nil) != tc.ok {
|
||||
t.Errorf("Check(%q) error = %v, want ok=%v", tc.raw, err, tc.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user