From b196ec4d8d556462bffafa7d2e32b0ffdca40900 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:18:12 +0200 Subject: [PATCH 1/9] Add passkeys support --- routers/web/auth/webauthn.go | 108 ++++++++++++++++++++++ routers/web/web.go | 2 + web_src/js/features/user-auth-webauthn.js | 2 +- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index 1079f44a085b..64676254c5e1 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -12,7 +12,9 @@ import ( wa "code.gitea.io/gitea/modules/auth/webauthn" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/externalaccount" @@ -47,6 +49,112 @@ func WebAuthn(ctx *context.Context) { ctx.HTML(http.StatusOK, tplWebAuthn) } +// WebAuthnLoginAssertion submits a WebAuthn challenge to the browser +func WebAuthnLoginAssertion1(ctx *context.Context) { + assertion, sessionData, err := wa.WebAuthn.BeginDiscoverableLogin() + if err != nil { + ctx.ServerError("webauthn.BeginDiscoverableLogin", err) + return + } + + if err := ctx.Session.Set("webauthnAssertion", sessionData); err != nil { + ctx.ServerError("Session.Set", err) + return + } + + ctx.JSON(http.StatusOK, assertion) +} + +// SignInPost response for sign in request +func WebAuthnLogin(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("sign_in") + + oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) + if err != nil { + ctx.ServerError("UserSignIn", err) + return + } + ctx.Data["OAuth2Providers"] = oauth2Providers + ctx.Data["Title"] = ctx.Tr("sign_in") + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" + ctx.Data["PageIsSignIn"] = true + ctx.Data["PageIsLogin"] = true + ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) + + if ctx.HasError() { + ctx.HTML(http.StatusOK, tplSignIn) + return + } + + sessionData, okData := ctx.Session.Get("webauthnAssertion").(*webauthn.SessionData) + if !okData || sessionData == nil { + ctx.ServerError("UserSignIn", errors.New("not in WebAuthn session")) + return + } + defer func() { + _ = ctx.Session.Delete("webauthnAssertion") + }() + + // Validate the parsed response. + // func(rawID, userHandle []byte) (user User, err error) + cred, err := wa.WebAuthn.FinishDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) { + user := &wa.User{} + // TODO: get actual user using rawID and userHandle from database + return user, nil + }, *sessionData, ctx.Req) + if err != nil { + // Failed authentication attempt. + log.Info("Failed authentication attempt for passkey from %s: %v", ctx.RemoteAddr(), err) + ctx.Status(http.StatusForbidden) + return + } + + userID := int64(1) + user, err := user_model.GetUserByID(ctx, userID) + if err != nil { + ctx.ServerError("UserSignIn", err) + return + } + + // Ensure that the credential wasn't cloned by checking if CloneWarning is set. + // (This is set if the sign counter is less than the one we have stored.) + if cred.Authenticator.CloneWarning { + log.Info("Failed authentication attempt for %s from %s: cloned credential", user.Name, ctx.RemoteAddr()) + ctx.Status(http.StatusForbidden) + return + } + + // Success! Get the credential and update the sign count with the new value we received. + dbCred, err := auth.GetWebAuthnCredentialByCredID(ctx, user.ID, cred.ID) + if err != nil { + ctx.ServerError("GetWebAuthnCredentialByCredID", err) + return + } + + dbCred.SignCount = cred.Authenticator.SignCount + if err := dbCred.UpdateSignCount(ctx); err != nil { + ctx.ServerError("UpdateSignCount", err) + return + } + + // Now handle account linking if that's requested + if ctx.Session.Get("linkAccount") != nil { + if err := externalaccount.LinkAccountFromStore(ctx, ctx.Session, user); err != nil { + ctx.ServerError("LinkAccountFromStore", err) + return + } + } + + remember := ctx.Session.Get("twofaRemember").(bool) + redirect := handleSignInFull(ctx, user, remember, false) + if redirect == "" { + redirect = setting.AppSubURL + "/" + } + _ = ctx.Session.Delete("twofaUid") + + ctx.JSONRedirect(redirect) +} + // WebAuthnLoginAssertion submits a WebAuthn challenge to the browser func WebAuthnLoginAssertion(ctx *context.Context) { // Ensure user is in a WebAuthn session. diff --git a/routers/web/web.go b/routers/web/web.go index 9f9a1bb0988e..db4fee89cb55 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -535,6 +535,8 @@ func registerRoutes(m *web.Router) { }) m.Group("/webauthn", func() { m.Get("", auth.WebAuthn) + m.Get("/passkey/assertion", auth.WebAuthnLoginAssertion1) + m.Get("/passkey/login", auth.WebAuthnLogin) m.Get("/assertion", auth.WebAuthnLoginAssertion) m.Post("/assertion", auth.WebAuthnLoginAssertionPost) }) diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index ea26614ba769..b1ec297f41c4 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -14,7 +14,7 @@ export async function initUserAuthWebAuthn() { return; } - const res = await GET(`${appSubUrl}/user/webauthn/assertion`); + const res = await GET(`${appSubUrl}/user/webauthn/passkey/assertion`); if (res.status !== 200) { webAuthnError('unknown'); return; From 03f1e5065b26d5774ad04101fd561ca577aa6e37 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:50:28 +0200 Subject: [PATCH 2/9] add login btn --- options/locale/locale_en-US.ini | 1 + templates/user/auth/signin_inner.tmpl | 6 ++++ web_src/js/features/user-auth-webauthn.js | 37 +++++++++++++++-------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 815cba6eecaf..d10f61f2ffc9 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -458,6 +458,7 @@ sspi_auth_failed = SSPI authentication failed password_pwned = The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too. password_pwned_err = Could not complete request to HaveIBeenPwned last_admin = You cannot remove the last admin. There must be at least one admin. +signin_passkey = Sign in with a passkey [mail] view_it_on = View it on %s diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index 9872096fbc6a..67528ebce329 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -9,6 +9,8 @@ {{end}}
+ {{template "user/auth/webauthn_error" .}} +
{{.CsrfTokenHtml}}
@@ -49,6 +51,10 @@
{{end}} + + {{if .OAuth2Providers}}
{{ctx.Locale.Tr "sign_in_or"}} diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index b1ec297f41c4..bfac70f4a16f 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -4,26 +4,21 @@ import {GET, POST} from '../modules/fetch.js'; const {appSubUrl} = window.config; -export async function initUserAuthWebAuthn() { - const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); - if (!elPrompt) { - return; - } - - if (!detectWebAuthnSupport()) { - return; - } - - const res = await GET(`${appSubUrl}/user/webauthn/passkey/assertion`); +async function doWebAuthn(assertionUrl) { + const res = await GET(assertionUrl); if (res.status !== 200) { webAuthnError('unknown'); return; } + const options = await res.json(); options.publicKey.challenge = decodeURLEncodedBase64(options.publicKey.challenge); - for (const cred of options.publicKey.allowCredentials) { + for (const cred of options.publicKey.allowCredentials ?? []) { cred.id = decodeURLEncodedBase64(cred.id); } + + console.log('leggo3', options); + try { const credential = await navigator.credentials.get({ publicKey: options.publicKey, @@ -46,6 +41,24 @@ export async function initUserAuthWebAuthn() { } } +export async function initUserAuthWebAuthn() { + console.log('moin initUserAuthWebAuthn'); + + if (!detectWebAuthnSupport()) { + return; + } + + const elSignInPasskeyBtn = document.querySelector('.signin-passkey'); + if (elSignInPasskeyBtn) { + elSignInPasskeyBtn.onclick = () => doWebAuthn(`${appSubUrl}/user/webauthn/passkey/assertion`); + } + + const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); + if (elPrompt) { + doWebAuthn(`${appSubUrl}/user/webauthn/assertion`); + } +} + async function verifyAssertion(assertedCredential) { // Move data into Arrays in case it is super long const authData = new Uint8Array(assertedCredential.response.authenticatorData); From 701e51b550623deb23f80eb4b70a0f6998be9482 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:25:39 +0200 Subject: [PATCH 3/9] finish --- modules/auth/webauthn/webauthn.go | 6 +- routers/web/auth/webauthn.go | 66 +++++++------ routers/web/user/setting/security/webauthn.go | 5 +- routers/web/web.go | 4 +- web_src/js/features/user-auth-webauthn.js | 92 ++++++++++++++----- 5 files changed, 112 insertions(+), 61 deletions(-) diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index 189d197333e0..db4b88892d18 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -31,9 +31,9 @@ func Init() { RPID: setting.Domain, RPOrigins: []string{appURL}, AuthenticatorSelection: protocol.AuthenticatorSelection{ - UserVerification: "discouraged", + UserVerification: protocol.VerificationDiscouraged, }, - AttestationPreference: protocol.PreferDirectAttestation, + AttestationPreference: protocol.PreferNoAttestation, }, } } @@ -66,7 +66,7 @@ func (u *User) WebAuthnIcon() string { return (*user_model.User)(u).AvatarLink(db.DefaultContext) } -// WebAuthnCredentials implementns the webauthn.User interface +// WebAuthnCredentials implements the webauthn.User interface func (u *User) WebAuthnCredentials() []webauthn.Credential { dbCreds, err := auth.GetWebAuthnCredentialsByUID(db.DefaultContext, u.ID) if err != nil { diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index 64676254c5e1..284b378cca4f 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -4,6 +4,7 @@ package auth import ( + "encoding/binary" "errors" "net/http" @@ -12,9 +13,7 @@ import ( wa "code.gitea.io/gitea/modules/auth/webauthn" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/externalaccount" @@ -49,15 +48,15 @@ func WebAuthn(ctx *context.Context) { ctx.HTML(http.StatusOK, tplWebAuthn) } -// WebAuthnLoginAssertion submits a WebAuthn challenge to the browser -func WebAuthnLoginAssertion1(ctx *context.Context) { +// WebAuthnPasskeyAssertion submits a WebAuthn challenge for the passkey login to the browser +func WebAuthnPasskeyAssertion(ctx *context.Context) { assertion, sessionData, err := wa.WebAuthn.BeginDiscoverableLogin() if err != nil { ctx.ServerError("webauthn.BeginDiscoverableLogin", err) return } - if err := ctx.Session.Set("webauthnAssertion", sessionData); err != nil { + if err := ctx.Session.Set("webauthnAssertion-passkey-todo", sessionData); err != nil { ctx.ServerError("Session.Set", err) return } @@ -65,30 +64,11 @@ func WebAuthnLoginAssertion1(ctx *context.Context) { ctx.JSON(http.StatusOK, assertion) } -// SignInPost response for sign in request -func WebAuthnLogin(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("sign_in") - - oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) - if err != nil { - ctx.ServerError("UserSignIn", err) - return - } - ctx.Data["OAuth2Providers"] = oauth2Providers - ctx.Data["Title"] = ctx.Tr("sign_in") - ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" - ctx.Data["PageIsSignIn"] = true - ctx.Data["PageIsLogin"] = true - ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) - - if ctx.HasError() { - ctx.HTML(http.StatusOK, tplSignIn) - return - } - - sessionData, okData := ctx.Session.Get("webauthnAssertion").(*webauthn.SessionData) +// WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey +func WebAuthnPasskeyLogin(ctx *context.Context) { + sessionData, okData := ctx.Session.Get("webauthnAssertion-passkey-todo").(*webauthn.SessionData) if !okData || sessionData == nil { - ctx.ServerError("UserSignIn", errors.New("not in WebAuthn session")) + ctx.ServerError("ctx.Session.Get", errors.New("not in WebAuthn session")) return } defer func() { @@ -96,11 +76,20 @@ func WebAuthnLogin(ctx *context.Context) { }() // Validate the parsed response. - // func(rawID, userHandle []byte) (user User, err error) + userID := int64(-1) cred, err := wa.WebAuthn.FinishDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) { - user := &wa.User{} - // TODO: get actual user using rawID and userHandle from database - return user, nil + var n int + userID, n = binary.Varint(userHandle) + if n <= 0 { + return nil, errors.New("invalid rawID") + } + + user, err := user_model.GetUserByID(ctx, userID) + if err != nil { + return nil, err + } + + return (*wa.User)(user), nil }, *sessionData, ctx.Req) if err != nil { // Failed authentication attempt. @@ -109,7 +98,16 @@ func WebAuthnLogin(ctx *context.Context) { return } - userID := int64(1) + if !cred.Flags.UserPresent { + ctx.Status(http.StatusBadRequest) + return + } + + if userID == -1 { + ctx.Status(http.StatusBadRequest) + return + } + user, err := user_model.GetUserByID(ctx, userID) if err != nil { ctx.ServerError("UserSignIn", err) @@ -145,7 +143,7 @@ func WebAuthnLogin(ctx *context.Context) { } } - remember := ctx.Session.Get("twofaRemember").(bool) + remember := false // TODO: implement remember me redirect := handleSignInFull(ctx, user, remember, false) if redirect == "" { redirect = setting.AppSubURL + "/" diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index e382c8b9af41..2ae189f2ca52 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -45,7 +45,10 @@ func WebAuthnRegister(ctx *context.Context) { return } - credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer)) + credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer), webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ + UserVerification: protocol.VerificationRequired, + ResidentKey: protocol.ResidentKeyRequirementRequired, + })) if err != nil { ctx.ServerError("Unable to BeginRegistration", err) return diff --git a/routers/web/web.go b/routers/web/web.go index db4fee89cb55..d08e8da77285 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -535,8 +535,8 @@ func registerRoutes(m *web.Router) { }) m.Group("/webauthn", func() { m.Get("", auth.WebAuthn) - m.Get("/passkey/assertion", auth.WebAuthnLoginAssertion1) - m.Get("/passkey/login", auth.WebAuthnLogin) + m.Get("/passkey/assertion", auth.WebAuthnPasskeyAssertion) + m.Post("/passkey/login", auth.WebAuthnPasskeyLogin) m.Get("/assertion", auth.WebAuthnLoginAssertion) m.Post("/assertion", auth.WebAuthnLoginAssertionPost) }) diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index bfac70f4a16f..4f51adc53de3 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -4,8 +4,24 @@ import {GET, POST} from '../modules/fetch.js'; const {appSubUrl} = window.config; -async function doWebAuthn(assertionUrl) { - const res = await GET(assertionUrl); +export async function initUserAuthWebAuthn() { + if (!detectWebAuthnSupport()) { + return; + } + + const elSignInPasskeyBtn = document.querySelector('.signin-passkey'); + if (elSignInPasskeyBtn) { + elSignInPasskeyBtn.addEventListener('click', loginPasskey); + } + + const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); + if (elPrompt) { + login2FA(); + } +} + +async function loginPasskey() { + const res = await GET(`${appSubUrl}/user/webauthn/passkey/assertion`); if (res.status !== 200) { webAuthnError('unknown'); return; @@ -17,7 +33,59 @@ async function doWebAuthn(assertionUrl) { cred.id = decodeURLEncodedBase64(cred.id); } - console.log('leggo3', options); + try { + const credential = await navigator.credentials.get({ + publicKey: options.publicKey, + }); + + // Move data into Arrays in case it is super long + const authData = new Uint8Array(credential.response.authenticatorData); + const clientDataJSON = new Uint8Array(credential.response.clientDataJSON); + const rawId = new Uint8Array(credential.rawId); + const sig = new Uint8Array(credential.response.signature); + const userHandle = new Uint8Array(credential.response.userHandle); + + const res = await POST(`${appSubUrl}/user/webauthn/passkey/login`, { + data: { + id: credential.id, + rawId: encodeURLEncodedBase64(rawId), + type: credential.type, + clientExtensionResults: credential.getClientExtensionResults(), + response: { + authenticatorData: encodeURLEncodedBase64(authData), + clientDataJSON: encodeURLEncodedBase64(clientDataJSON), + signature: encodeURLEncodedBase64(sig), + userHandle: encodeURLEncodedBase64(userHandle), + }, + }, + }); + if (res.status === 500) { + webAuthnError('unknown'); + return; + } else if (res.status !== 200) { + webAuthnError('unable-to-process'); + return; + } + const reply = await res.json(); + + window.location.href = reply?.redirect ?? `${appSubUrl}/`; + } catch (err) { + webAuthnError('general', err.message); + } +} + +async function login2FA() { + const res = await GET(`${appSubUrl}/user/webauthn/assertion`); + if (res.status !== 200) { + webAuthnError('unknown'); + return; + } + + const options = await res.json(); + options.publicKey.challenge = decodeURLEncodedBase64(options.publicKey.challenge); + for (const cred of options.publicKey.allowCredentials ?? []) { + cred.id = decodeURLEncodedBase64(cred.id); + } try { const credential = await navigator.credentials.get({ @@ -41,24 +109,6 @@ async function doWebAuthn(assertionUrl) { } } -export async function initUserAuthWebAuthn() { - console.log('moin initUserAuthWebAuthn'); - - if (!detectWebAuthnSupport()) { - return; - } - - const elSignInPasskeyBtn = document.querySelector('.signin-passkey'); - if (elSignInPasskeyBtn) { - elSignInPasskeyBtn.onclick = () => doWebAuthn(`${appSubUrl}/user/webauthn/passkey/assertion`); - } - - const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); - if (elPrompt) { - doWebAuthn(`${appSubUrl}/user/webauthn/assertion`); - } -} - async function verifyAssertion(assertedCredential) { // Move data into Arrays in case it is super long const authData = new Uint8Array(assertedCredential.response.authenticatorData); From b4faa4ae151b8b89b5c303df1dd696ae1387f180 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:41:17 +0200 Subject: [PATCH 4/9] simplify changes --- modules/auth/webauthn/webauthn.go | 2 +- routers/web/user/setting/security/webauthn.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index db4b88892d18..790006ee567c 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -33,7 +33,7 @@ func Init() { AuthenticatorSelection: protocol.AuthenticatorSelection{ UserVerification: protocol.VerificationDiscouraged, }, - AttestationPreference: protocol.PreferNoAttestation, + AttestationPreference: protocol.PreferDirectAttestation, }, } } diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 2ae189f2ca52..1b8d0171f563 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -46,8 +46,7 @@ func WebAuthnRegister(ctx *context.Context) { } credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer), webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ - UserVerification: protocol.VerificationRequired, - ResidentKey: protocol.ResidentKeyRequirementRequired, + ResidentKey: protocol.ResidentKeyRequirementRequired, })) if err != nil { ctx.ServerError("Unable to BeginRegistration", err) From 9b9a140599729a9cf92a669ae58a92615e5a2dc0 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:50:07 +0200 Subject: [PATCH 5/9] fix typo --- models/auth/webauthn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index a65d2e1e343d..553130ee2e9e 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -181,7 +181,7 @@ func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) { return had > 0, err } -// WebAuthnCredentials implementns the webauthn.User interface +// WebAuthnCredentials implements the webauthn.User interface func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) { dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID) if err != nil { From eda2056798ac79e8efb01969a5f23070121f7162 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:50:15 +0200 Subject: [PATCH 6/9] use res.ok --- web_src/js/features/user-auth-webauthn.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index 4f51adc53de3..ade452c68d6b 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -22,7 +22,7 @@ export async function initUserAuthWebAuthn() { async function loginPasskey() { const res = await GET(`${appSubUrl}/user/webauthn/passkey/assertion`); - if (res.status !== 200) { + if (!res.ok) { webAuthnError('unknown'); return; } @@ -62,7 +62,7 @@ async function loginPasskey() { if (res.status === 500) { webAuthnError('unknown'); return; - } else if (res.status !== 200) { + } else if (!res.ok) { webAuthnError('unable-to-process'); return; } @@ -134,7 +134,7 @@ async function verifyAssertion(assertedCredential) { if (res.status === 500) { webAuthnError('unknown'); return; - } else if (res.status !== 200) { + } else if (!res.ok) { webAuthnError('unable-to-process'); return; } @@ -230,7 +230,7 @@ async function webAuthnRegisterRequest() { if (res.status === 409) { webAuthnError('duplicated'); return; - } else if (res.status !== 200) { + } else if (!res.ok) { webAuthnError('unknown'); return; } From ddc8ef1b5bc2bfe356bfd9825b889eecd5ddb197 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:51:17 +0200 Subject: [PATCH 7/9] rm href --- templates/user/auth/signin_inner.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index 67528ebce329..51e0e3b98256 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -52,7 +52,7 @@ {{end}} {{if .OAuth2Providers}} From a050b806b0f838263b8c3254f19ac97400c022d7 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:57:17 +0200 Subject: [PATCH 8/9] cleanup --- routers/web/auth/webauthn.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index 284b378cca4f..3160c5e23f03 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -56,7 +56,7 @@ func WebAuthnPasskeyAssertion(ctx *context.Context) { return } - if err := ctx.Session.Set("webauthnAssertion-passkey-todo", sessionData); err != nil { + if err := ctx.Session.Set("webauthnPasskeyAssertion", sessionData); err != nil { ctx.ServerError("Session.Set", err) return } @@ -66,25 +66,25 @@ func WebAuthnPasskeyAssertion(ctx *context.Context) { // WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey func WebAuthnPasskeyLogin(ctx *context.Context) { - sessionData, okData := ctx.Session.Get("webauthnAssertion-passkey-todo").(*webauthn.SessionData) + sessionData, okData := ctx.Session.Get("webauthnPasskeyAssertion").(*webauthn.SessionData) if !okData || sessionData == nil { ctx.ServerError("ctx.Session.Get", errors.New("not in WebAuthn session")) return } defer func() { - _ = ctx.Session.Delete("webauthnAssertion") + _ = ctx.Session.Delete("webauthnPasskeyAssertion") }() // Validate the parsed response. - userID := int64(-1) + var user *user_model.User cred, err := wa.WebAuthn.FinishDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) { - var n int - userID, n = binary.Varint(userHandle) + userID, n := binary.Varint(userHandle) if n <= 0 { return nil, errors.New("invalid rawID") } - user, err := user_model.GetUserByID(ctx, userID) + var err error + user, err = user_model.GetUserByID(ctx, userID) if err != nil { return nil, err } @@ -103,17 +103,11 @@ func WebAuthnPasskeyLogin(ctx *context.Context) { return } - if userID == -1 { + if user == nil { ctx.Status(http.StatusBadRequest) return } - user, err := user_model.GetUserByID(ctx, userID) - if err != nil { - ctx.ServerError("UserSignIn", err) - return - } - // Ensure that the credential wasn't cloned by checking if CloneWarning is set. // (This is set if the sign counter is less than the one we have stored.) if cred.Authenticator.CloneWarning { @@ -148,7 +142,6 @@ func WebAuthnPasskeyLogin(ctx *context.Context) { if redirect == "" { redirect = setting.AppSubURL + "/" } - _ = ctx.Session.Delete("twofaUid") ctx.JSONRedirect(redirect) } From 1dfccd6582e1db36b60f611fc3af94fa79cc0539 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:44:13 +0200 Subject: [PATCH 9/9] Update web_src/js/features/user-auth-webauthn.js Co-authored-by: silverwind --- web_src/js/features/user-auth-webauthn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index ade452c68d6b..a317fee7e276 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -76,7 +76,7 @@ async function loginPasskey() { async function login2FA() { const res = await GET(`${appSubUrl}/user/webauthn/assertion`); - if (res.status !== 200) { + if (!res.ok) { webAuthnError('unknown'); return; }