fixed passkey
This commit is contained in:
parent
df52f94868
commit
724fbf52c9
@ -112,8 +112,17 @@ func authAllowedRPOrigins() []string {
|
|||||||
out = append(out, origin)
|
out = append(out, origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, origin := range configuredPublicAppOrigins() {
|
||||||
|
addOrigin(origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "https"
|
||||||
|
if !httpsEnabled() {
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
for _, host := range localAppHostCandidates() {
|
for _, host := range localAppHostCandidates() {
|
||||||
addOrigin(originForHost("https", host, appPort()))
|
addOrigin(originForHost(scheme, host, appPort()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit env values remain supported, but they extend the dynamic list
|
// Explicit env values remain supported, but they extend the dynamic list
|
||||||
@ -125,6 +134,33 @@ func authAllowedRPOrigins() []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configuredPublicAppOrigins() []string {
|
||||||
|
keys := []string{
|
||||||
|
"APP_PUBLIC_URLS",
|
||||||
|
"APP_PUBLIC_URL",
|
||||||
|
"PUBLIC_APP_URLS",
|
||||||
|
"PUBLIC_APP_URL",
|
||||||
|
"NSFWAPP_URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]string, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(key))
|
||||||
|
if raw == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range strings.Split(raw, ",") {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part != "" {
|
||||||
|
out = append(out, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func configuredAuthOrigins() []string {
|
func configuredAuthOrigins() []string {
|
||||||
raw := strings.TrimSpace(os.Getenv("AUTH_RP_ORIGINS"))
|
raw := strings.TrimSpace(os.Getenv("AUTH_RP_ORIGINS"))
|
||||||
|
|
||||||
@ -381,6 +417,57 @@ func originIsAllowed(origin string, allowed []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requestHostOriginAllowed(origin string, r *http.Request) bool {
|
||||||
|
normalizedOrigin, ok := canonicalOrigin(origin)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(normalizedOrigin)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
originHost := normalizeOriginHost(u.Hostname())
|
||||||
|
requestHost, requestPort := splitRequestHostPort(r.Host)
|
||||||
|
if originHost == "" || requestHost == "" || originHost != requestHost {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(u.Port()) != requestPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "https" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Scheme == "http" && isLocalWebAuthnHost(originHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitRequestHostPort(host string) (string, string) {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if host == "" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, p, err := net.SplitHostPort(host); err == nil {
|
||||||
|
return normalizeOriginHost(h), strings.TrimSpace(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeOriginHost(host), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLocalWebAuthnHost(host string) bool {
|
||||||
|
host = normalizeOriginHost(host)
|
||||||
|
if host == "localhost" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
return ip != nil && ip.IsLoopback()
|
||||||
|
}
|
||||||
|
|
||||||
func (am *AuthManager) webAuthnForRequest(r *http.Request) (*webauthn.WebAuthn, error) {
|
func (am *AuthManager) webAuthnForRequest(r *http.Request) (*webauthn.WebAuthn, error) {
|
||||||
origin := originFromRequest(r)
|
origin := originFromRequest(r)
|
||||||
if origin == "" {
|
if origin == "" {
|
||||||
@ -388,7 +475,7 @@ func (am *AuthManager) webAuthnForRequest(r *http.Request) (*webauthn.WebAuthn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
allowedOrigins := authAllowedRPOrigins()
|
allowedOrigins := authAllowedRPOrigins()
|
||||||
if !originIsAllowed(origin, allowedOrigins) {
|
if !originIsAllowed(origin, allowedOrigins) && !requestHostOriginAllowed(origin, r) {
|
||||||
return nil, errors.New("origin not allowed for WebAuthn: " + origin)
|
return nil, errors.New("origin not allowed for WebAuthn: " + origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
backend/auth_passkey_test.go
Normal file
32
backend/auth_passkey_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRequestHostOriginAllowedAcceptsHTTPSFQDN(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "https://l14pbbk95100006.tegdssd.de:9999/api/auth/passkey/login/options", nil)
|
||||||
|
|
||||||
|
if !requestHostOriginAllowed("https://l14pbbk95100006.tegdssd.de:9999", r) {
|
||||||
|
t.Fatal("expected matching HTTPS request host origin to be allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestHostOriginAllowedRejectsDifferentHost(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "https://l14pbbk95100006.tegdssd.de:9999/api/auth/passkey/login/options", nil)
|
||||||
|
|
||||||
|
if requestHostOriginAllowed("https://evil.example:9999", r) {
|
||||||
|
t.Fatal("expected different origin host to be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthAllowedRPOriginsIncludesConfiguredPublicURL(t *testing.T) {
|
||||||
|
t.Setenv("APP_PUBLIC_URL", "https://l14pbbk95100006.tegdssd.de:9999")
|
||||||
|
|
||||||
|
origins := authAllowedRPOrigins()
|
||||||
|
if !originIsAllowed("https://l14pbbk95100006.tegdssd.de:9999", origins) {
|
||||||
|
t.Fatalf("expected configured public URL in allowed origins: %#v", origins)
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp_amd64.deb
vendored
BIN
backend/dist/nsfwapp_amd64.deb
vendored
Binary file not shown.
@ -941,15 +941,23 @@ func publicAppURLs() []string {
|
|||||||
out := make([]string, 0, 8)
|
out := make([]string, 0, 8)
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
|
|
||||||
for _, host := range localAppHostCandidates() {
|
addURL := func(raw string) {
|
||||||
u := originForHost(scheme, host, port)
|
u, ok := canonicalOrigin(raw)
|
||||||
if u == "" || seen[u] {
|
if !ok || seen[u] {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
seen[u] = true
|
seen[u] = true
|
||||||
out = append(out, u)
|
out = append(out, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, u := range configuredPublicAppOrigins() {
|
||||||
|
addURL(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range localAppHostCandidates() {
|
||||||
|
addURL(originForHost(scheme, host, port))
|
||||||
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,7 +115,7 @@ export default function LoginPage({ onLoggedIn }: Props) {
|
|||||||
if (raw.includes('passkey registration verify failed')) {
|
if (raw.includes('passkey registration verify failed')) {
|
||||||
return (
|
return (
|
||||||
raw +
|
raw +
|
||||||
'\n\nPrüfe AUTH_RP_ORIGIN und AUTH_RP_ID. Die Werte müssen exakt zur Browser-URL passen.'
|
'\n\nPruefe, ob du den Passkey unter genau dieser Browser-URL registriert hast. Passkeys sind an Hostname und RP-ID gebunden.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ export default function LoginPage({ onLoggedIn }: Props) {
|
|||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
raw +
|
raw +
|
||||||
'\n\nDas sieht nach einem Origin/RP-ID Problem aus. Prüfe AUTH_RP_ORIGIN und AUTH_RP_ID.'
|
'\n\nDas sieht nach einem Origin/RP-ID Problem aus. Pruefe, ob Hostname, HTTPS und die Passkey-Registrierung zusammenpassen.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1891,7 +1891,7 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) {
|
|||||||
if (raw.includes('passkey registration verify failed')) {
|
if (raw.includes('passkey registration verify failed')) {
|
||||||
return (
|
return (
|
||||||
raw +
|
raw +
|
||||||
'\n\nPrüfe AUTH_RP_ORIGIN und AUTH_RP_ID. Die Werte müssen exakt zur Browser-URL passen.'
|
'\n\nPruefe, ob du den Passkey unter genau dieser Browser-URL registriert hast. Passkeys sind an Hostname und RP-ID gebunden.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1911,7 +1911,7 @@ export default function RecorderSettings({ onAssetsGenerated }: Props) {
|
|||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
raw +
|
raw +
|
||||||
'\n\nDas sieht nach einem Origin/RP-ID Problem aus. Prüfe AUTH_RP_ORIGIN und AUTH_RP_ID.'
|
'\n\nDas sieht nach einem Origin/RP-ID Problem aus. Pruefe, ob Hostname, HTTPS und die Passkey-Registrierung zusammenpassen.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user