refactor(auth): replace dashboard sessions with stateless tokens and session control (#6329)
* refactor(auth): replace dashboard sessions with stateless tokens * feat(auth): harden session issuance and distributed enforcement * fix(proxy): preserve trusted proxy compatibility defaults * refactor: address dashboard auth review feedback * refactor: remove classic frontend and flatten web app
This commit is contained in:
@@ -2,11 +2,45 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NormalizeOrigin validates and canonicalizes a browser origin. Only an exact
|
||||
// scheme/host/effective-port match is meaningful; paths and wildcards are not
|
||||
// accepted for authentication cookie endpoints.
|
||||
func NormalizeOrigin(raw string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" || raw == "null" || strings.ContainsAny(raw, "\r\n") {
|
||||
return "", fmt.Errorf("origin is empty or invalid")
|
||||
}
|
||||
parsedURL, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid origin: %w", err)
|
||||
}
|
||||
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||
return "", fmt.Errorf("origin scheme must be http or https")
|
||||
}
|
||||
if parsedURL.Host == "" || parsedURL.User != nil || parsedURL.RawQuery != "" || parsedURL.Fragment != "" || (parsedURL.Path != "" && parsedURL.Path != "/") {
|
||||
return "", fmt.Errorf("origin must contain only scheme and host")
|
||||
}
|
||||
hostname := strings.ToLower(parsedURL.Hostname())
|
||||
if hostname == "" || strings.Contains(hostname, "*") {
|
||||
return "", fmt.Errorf("origin host is empty")
|
||||
}
|
||||
port := parsedURL.Port()
|
||||
normalizedHost := hostname
|
||||
if strings.Contains(hostname, ":") {
|
||||
normalizedHost = "[" + hostname + "]"
|
||||
}
|
||||
if port == "" || (parsedURL.Scheme == "http" && port == "80") || (parsedURL.Scheme == "https" && port == "443") {
|
||||
return parsedURL.Scheme + "://" + normalizedHost, nil
|
||||
}
|
||||
return parsedURL.Scheme + "://" + net.JoinHostPort(hostname, port), nil
|
||||
}
|
||||
|
||||
func InitSessionCookieSettings() error {
|
||||
secureRaw := strings.TrimSpace(os.Getenv("SESSION_COOKIE_SECURE"))
|
||||
trustedURLsRaw := strings.TrimSpace(os.Getenv("SESSION_COOKIE_TRUSTED_URL"))
|
||||
@@ -35,14 +69,14 @@ func InitSessionCookieSettings() error {
|
||||
if trustedURL == "" {
|
||||
return fmt.Errorf("SESSION_COOKIE_TRUSTED_URL contains an empty URL")
|
||||
}
|
||||
parsedURL, err := url.Parse(trustedURL)
|
||||
normalizedOrigin, err := NormalizeOrigin(trustedURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid SESSION_COOKIE_TRUSTED_URL: %w", err)
|
||||
}
|
||||
if parsedURL.Scheme != "https" || parsedURL.Host == "" {
|
||||
if !strings.HasPrefix(normalizedOrigin, "https://") {
|
||||
return fmt.Errorf("SESSION_COOKIE_TRUSTED_URL must contain only https URLs with hosts")
|
||||
}
|
||||
SessionCookieTrustedURLs = append(SessionCookieTrustedURLs, trustedURL)
|
||||
SessionCookieTrustedURLs = append(SessionCookieTrustedURLs, normalizedOrigin)
|
||||
}
|
||||
|
||||
SessionCookieSecure = true
|
||||
|
||||
Reference in New Issue
Block a user