|
4 | 4 | package auth
|
5 | 5 |
|
6 | 6 | import (
|
| 7 | + "encoding/binary" |
7 | 8 | "errors"
|
8 | 9 | "net/http"
|
9 | 10 |
|
@@ -47,6 +48,104 @@ func WebAuthn(ctx *context.Context) {
|
47 | 48 | ctx.HTML(http.StatusOK, tplWebAuthn)
|
48 | 49 | }
|
49 | 50 |
|
| 51 | +// WebAuthnPasskeyAssertion submits a WebAuthn challenge for the passkey login to the browser |
| 52 | +func WebAuthnPasskeyAssertion(ctx *context.Context) { |
| 53 | + assertion, sessionData, err := wa.WebAuthn.BeginDiscoverableLogin() |
| 54 | + if err != nil { |
| 55 | + ctx.ServerError("webauthn.BeginDiscoverableLogin", err) |
| 56 | + return |
| 57 | + } |
| 58 | + |
| 59 | + if err := ctx.Session.Set("webauthnPasskeyAssertion", sessionData); err != nil { |
| 60 | + ctx.ServerError("Session.Set", err) |
| 61 | + return |
| 62 | + } |
| 63 | + |
| 64 | + ctx.JSON(http.StatusOK, assertion) |
| 65 | +} |
| 66 | + |
| 67 | +// WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey |
| 68 | +func WebAuthnPasskeyLogin(ctx *context.Context) { |
| 69 | + sessionData, okData := ctx.Session.Get("webauthnPasskeyAssertion").(*webauthn.SessionData) |
| 70 | + if !okData || sessionData == nil { |
| 71 | + ctx.ServerError("ctx.Session.Get", errors.New("not in WebAuthn session")) |
| 72 | + return |
| 73 | + } |
| 74 | + defer func() { |
| 75 | + _ = ctx.Session.Delete("webauthnPasskeyAssertion") |
| 76 | + }() |
| 77 | + |
| 78 | + // Validate the parsed response. |
| 79 | + var user *user_model.User |
| 80 | + cred, err := wa.WebAuthn.FinishDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) { |
| 81 | + userID, n := binary.Varint(userHandle) |
| 82 | + if n <= 0 { |
| 83 | + return nil, errors.New("invalid rawID") |
| 84 | + } |
| 85 | + |
| 86 | + var err error |
| 87 | + user, err = user_model.GetUserByID(ctx, userID) |
| 88 | + if err != nil { |
| 89 | + return nil, err |
| 90 | + } |
| 91 | + |
| 92 | + return (*wa.User)(user), nil |
| 93 | + }, *sessionData, ctx.Req) |
| 94 | + if err != nil { |
| 95 | + // Failed authentication attempt. |
| 96 | + log.Info("Failed authentication attempt for passkey from %s: %v", ctx.RemoteAddr(), err) |
| 97 | + ctx.Status(http.StatusForbidden) |
| 98 | + return |
| 99 | + } |
| 100 | + |
| 101 | + if !cred.Flags.UserPresent { |
| 102 | + ctx.Status(http.StatusBadRequest) |
| 103 | + return |
| 104 | + } |
| 105 | + |
| 106 | + if user == nil { |
| 107 | + ctx.Status(http.StatusBadRequest) |
| 108 | + return |
| 109 | + } |
| 110 | + |
| 111 | + // Ensure that the credential wasn't cloned by checking if CloneWarning is set. |
| 112 | + // (This is set if the sign counter is less than the one we have stored.) |
| 113 | + if cred.Authenticator.CloneWarning { |
| 114 | + log.Info("Failed authentication attempt for %s from %s: cloned credential", user.Name, ctx.RemoteAddr()) |
| 115 | + ctx.Status(http.StatusForbidden) |
| 116 | + return |
| 117 | + } |
| 118 | + |
| 119 | + // Success! Get the credential and update the sign count with the new value we received. |
| 120 | + dbCred, err := auth.GetWebAuthnCredentialByCredID(ctx, user.ID, cred.ID) |
| 121 | + if err != nil { |
| 122 | + ctx.ServerError("GetWebAuthnCredentialByCredID", err) |
| 123 | + return |
| 124 | + } |
| 125 | + |
| 126 | + dbCred.SignCount = cred.Authenticator.SignCount |
| 127 | + if err := dbCred.UpdateSignCount(ctx); err != nil { |
| 128 | + ctx.ServerError("UpdateSignCount", err) |
| 129 | + return |
| 130 | + } |
| 131 | + |
| 132 | + // Now handle account linking if that's requested |
| 133 | + if ctx.Session.Get("linkAccount") != nil { |
| 134 | + if err := externalaccount.LinkAccountFromStore(ctx, ctx.Session, user); err != nil { |
| 135 | + ctx.ServerError("LinkAccountFromStore", err) |
| 136 | + return |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + remember := false // TODO: implement remember me |
| 141 | + redirect := handleSignInFull(ctx, user, remember, false) |
| 142 | + if redirect == "" { |
| 143 | + redirect = setting.AppSubURL + "/" |
| 144 | + } |
| 145 | + |
| 146 | + ctx.JSONRedirect(redirect) |
| 147 | +} |
| 148 | + |
50 | 149 | // WebAuthnLoginAssertion submits a WebAuthn challenge to the browser
|
51 | 150 | func WebAuthnLoginAssertion(ctx *context.Context) {
|
52 | 151 | // Ensure user is in a WebAuthn session.
|
|
0 commit comments