From a73519f5f831f0a7221fd2908b6d17817768e66d Mon Sep 17 00:00:00 2001 From: mStar Date: Sat, 29 Mar 2025 17:51:12 +0100 Subject: [PATCH] Passkey support for login. Maybe Can only see once full swap to new systems --- auth-new/auth.go | 12 ++++ auth-new/errors.go | 1 + auth-new/passkey.go | 147 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 157 insertions(+), 3 deletions(-) diff --git a/auth-new/auth.go b/auth-new/auth.go index 377ca0f..1dcce0a 100644 --- a/auth-new/auth.go +++ b/auth-new/auth.go @@ -1,9 +1,21 @@ +// Package auth is responsible for everything authentication +// +// Be that checking login data and handing out an access token on sucess, +// checking if a given access token can do the requested action +// or adding or updating the authentication information of an account. +// And I probably forgot something package auth import ( + "time" + "github.com/go-webauthn/webauthn/webauthn" ) type Authenticator struct { webauthn *webauthn.WebAuthn } + +func calcAccessExpirationTimestamp() time.Time { + return time.Now().Add(time.Hour * 24 * 30) +} diff --git a/auth-new/errors.go b/auth-new/errors.go index 0eea820..7922390 100644 --- a/auth-new/errors.go +++ b/auth-new/errors.go @@ -8,4 +8,5 @@ var ( // The user hasn't setup the provided authentication method ErrUnsupportedAuthMethod = errors.New("authentication method not supported for this user") ErrInvalidCombination = errors.New("invalid account and token combination") + ErrProcessTimeout = errors.New("authentication process timed out") ) diff --git a/auth-new/passkey.go b/auth-new/passkey.go index ed5ef60..30a34ad 100644 --- a/auth-new/passkey.go +++ b/auth-new/passkey.go @@ -7,6 +7,8 @@ import ( "git.mstar.dev/mstar/goutils/other" "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/webauthn" + "gorm.io/gorm/clause" "git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/models" @@ -28,21 +30,160 @@ func (a *Authenticator) StartPasskeyLogin(username string) (*protocol.Credential ExpiresAt: time.Now().Add(time.Minute * 3), Token: string(other.Must(json.Marshal(session))), } - err = dbgen.LoginProcessToken.Create(&pkeySession) + err = dbgen.LoginProcessToken.Clauses(clause.OnConflict{UpdateAll: true}).Create(&pkeySession) if err != nil { return nil, err } return options, nil } -func (a *Authenticator) CompletePasskeyLogin(username string, response *http.Request) error { - panic("Not implemented") // TODO: Implement me +func (a *Authenticator) CompletePasskeyLogin( + username string, + response *http.Request, +) (accessToken string, err error) { + // Get user in question + acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First() + if err != nil { + return "", other.Error("auth", "failed to get user for passkey login completion", err) + } + // Get latest login token data + loginToken, err := dbgen.LoginProcessToken.Where(dbgen.LoginProcessToken.UserId.Eq(acc.ID)). + First() + if err != nil { + return "", other.Error( + "auth", + "failed to get user's login token for passkey login completion", + err, + ) + } + // Check if that token has expired + if loginToken.ExpiresAt.Before(time.Now()) { + return "", ErrProcessTimeout + } + var pkeySession webauthn.SessionData + err = json.Unmarshal([]byte(loginToken.Token), &pkeySession) + if err != nil { + return "", other.Error("auth", "failed to unmarshal passkey session for user", err) + } + // Hand data to webauthn for completion + newSession, err := a.webauthn.FinishLogin(&fakeUser{acc}, pkeySession, response) + jsonSession, err := json.Marshal(newSession) + if err != nil { + return "", err + } + // Update credentials + _, err = dbgen.UserAuthMethod.Where(dbgen.UserAuthMethod.Token.Like("%"+string(jsonSession)+"%")). + Update(dbgen.UserAuthMethod.Token, jsonSession) + if err != nil { + return "", err + } + // And delete the login token + dbgen.LoginProcessToken.Where(dbgen.LoginProcessToken.UserId.Eq(acc.ID)).Delete(loginToken) + dbAccessToken := models.AccessToken{ + User: *acc, + UserId: acc.ID, + ExpiresAt: calcAccessExpirationTimestamp(), + } + err = dbgen.AccessToken.Omit(dbgen.AccessToken.Token).Create(&dbAccessToken) + if err != nil { + return "", err + } + + return dbAccessToken.Token, nil } func (a *Authenticator) StartPasskeyRegistration(username string) error { + // p.l.Infof("begin registration") + // + // // can we actually do not use the username at all? + // username, err := getUsername(r) + // + // if err != nil { + // p.l.Errorf("can't get username: %s", err.Error()) + // JSONResponse(w, fmt.Sprintf("can't get username: %s", err.Error()), http.StatusBadRequest) + // + // return + // } + // + // user := p.userStore.GetOrCreateUser(username) + // + // options, session, err := p.webAuthn.BeginRegistration(user) + // + // if err != nil { + // msg := fmt.Sprintf("can't begin registration: %s", err.Error()) + // p.l.Errorf(msg) + // JSONResponse(w, msg, http.StatusBadRequest) + // + // return + // } + // + // // Make a session key and store the sessionData values + // t, err := p.sessionStore.GenSessionID() + // + // if err != nil { + // p.l.Errorf("can't generate session id: %s", err.Error()) + // JSONResponse( + // w, + // fmt.Sprintf("can't generate session id: %s", err.Error()), + // http.StatusInternalServerError, + // ) + // + // return + // } + // + // p.sessionStore.SaveSession(t, session) + // p.setSessionCookie(w, t) + // + // // return the options generated with the session key + // // options.publicKey contain our registration options + // JSONResponse(w, options, http.StatusOK) + panic("Not implemented") // TODO: Implement me } func (a *Authenticator) CompletePasskeyRegistration(username string) error { + // // Get the session key from cookie + // sid, err := r.Cookie(p.cookieSettings.Name) + // + // if err != nil { + // p.l.Errorf("can't get session id: %s", err.Error()) + // JSONResponse(w, fmt.Sprintf("can't get session id: %s", err.Error()), http.StatusBadRequest) + // + // return + // } + // + // // Get the session data stored from the function above + // session, ok := p.sessionStore.GetSession(sid.Value) + // + // if !ok { + // p.l.Errorf("can't get session data") + // JSONResponse(w, "can't get session data", http.StatusBadRequest) + // + // return + // } + // + // user := p.userStore.GetUserByWebAuthnId(session.UserID) // Get the user + // + // credential, err := p.webAuthn.FinishRegistration(user, *session, r) + // + // if err != nil { + // msg := fmt.Sprintf("can't finish registration: %s", err.Error()) + // p.l.Errorf(msg) + // + // p.deleteSessionCookie(w) + // JSONResponse(w, msg, http.StatusBadRequest) + // + // return + // } + // + // // If creation was successful, store the credential object + // user.PutCredential(*credential) + // p.userStore.SaveUser(user) + // + // p.sessionStore.DeleteSession(sid.Value) + // p.deleteSessionCookie(w) + // + // p.l.Infof("finish registration") + // JSONResponse(w, "Registration Success", http.StatusOK) panic("Not implemented") // TODO: Implement me }