linstrom/auth-new/passkey.go
2025-03-31 08:07:16 +02:00

192 lines
5.5 KiB
Go

package auth
import (
"encoding/json"
"net/http"
"time"
"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"
)
func (a *Authenticator) StartPasskeyLogin(username string) (*protocol.CredentialAssertion, error) {
if ok, err := a.canUsernameLogin(username); !ok {
return nil, other.Error("auth", "user may not login", err)
}
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
if err != nil {
return nil, err
}
wrappedAcc := fakeUser{acc}
options, session, err := a.webauthn.BeginLogin(&wrappedAcc)
if err != nil {
return nil, err
}
pkeySession := models.LoginProcessToken{
User: *acc,
UserId: acc.ID,
ExpiresAt: time.Now().Add(time.Minute * 3),
Token: string(other.Must(json.Marshal(session))),
}
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,
) (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
}