Passkey support for login. Maybe
Can only see once full swap to new systems
This commit is contained in:
parent
2c2f7deb9a
commit
a73519f5f8
3 changed files with 157 additions and 3 deletions
|
@ -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
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Authenticator struct {
|
type Authenticator struct {
|
||||||
webauthn *webauthn.WebAuthn
|
webauthn *webauthn.WebAuthn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calcAccessExpirationTimestamp() time.Time {
|
||||||
|
return time.Now().Add(time.Hour * 24 * 30)
|
||||||
|
}
|
||||||
|
|
|
@ -8,4 +8,5 @@ var (
|
||||||
// The user hasn't setup the provided authentication method
|
// The user hasn't setup the provided authentication method
|
||||||
ErrUnsupportedAuthMethod = errors.New("authentication method not supported for this user")
|
ErrUnsupportedAuthMethod = errors.New("authentication method not supported for this user")
|
||||||
ErrInvalidCombination = errors.New("invalid account and token combination")
|
ErrInvalidCombination = errors.New("invalid account and token combination")
|
||||||
|
ErrProcessTimeout = errors.New("authentication process timed out")
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
|
|
||||||
"git.mstar.dev/mstar/goutils/other"
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"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/dbgen"
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
"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),
|
ExpiresAt: time.Now().Add(time.Minute * 3),
|
||||||
Token: string(other.Must(json.Marshal(session))),
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return options, nil
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) CompletePasskeyLogin(username string, response *http.Request) error {
|
func (a *Authenticator) CompletePasskeyLogin(
|
||||||
panic("Not implemented") // TODO: Implement me
|
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 {
|
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
|
panic("Not implemented") // TODO: Implement me
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) CompletePasskeyRegistration(username string) error {
|
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
|
panic("Not implemented") // TODO: Implement me
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue