Password login & registration Passkey login May not be functional yet
This commit is contained in:
parent
b84461d0e7
commit
a9af73b557
3 changed files with 159 additions and 80 deletions
|
@ -9,15 +9,96 @@ package auth
|
|||
import (
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
webauthn *webauthn.WebAuthn
|
||||
}
|
||||
type LoginNextState uint8
|
||||
|
||||
const (
|
||||
LoginNextFailure LoginNextState = 0 // Login failed (default state)
|
||||
LoginNextSucess LoginNextState = 1 << iota // Login suceeded
|
||||
LoginUnknown // Unknown login method type, should result in failure
|
||||
LoginNext2FaTotp // Login requires a totp token next as 2fa response
|
||||
LoginNext2FaPasskey // Login requires a passkey token next as 2fa response
|
||||
LoginNext2FaMail // Login requires an email token next as 2fa response
|
||||
LoginStartPassword // Login starts with a password
|
||||
LoginStartPasskey // Login starts with a passkey
|
||||
)
|
||||
|
||||
func calcAccessExpirationTimestamp() time.Time {
|
||||
// For now, the default expiration is one month after creation
|
||||
// though "never" might also be a good option
|
||||
return time.Now().Add(time.Hour * 24 * 30)
|
||||
}
|
||||
|
||||
func ConvertNewStorageAuthMethodsToLoginState(
|
||||
methods []models.AuthenticationMethodType,
|
||||
isStart bool,
|
||||
) LoginNextState {
|
||||
translatedMethods := sliceutils.Map(methods, oneStorageAuthToLoginState)
|
||||
// Filter out only the valid methods for the current request
|
||||
valids := sliceutils.Filter(translatedMethods, func(t LoginNextState) bool {
|
||||
if isStart {
|
||||
return t == LoginStartPasskey || t == LoginStartPassword
|
||||
} else {
|
||||
return t == LoginNext2FaTotp || t == LoginNext2FaPasskey || t == LoginNext2FaMail
|
||||
}
|
||||
})
|
||||
// And then compact them down into one bit flag
|
||||
return sliceutils.Compact(
|
||||
valids,
|
||||
func(acc, next LoginNextState) LoginNextState { return acc | next },
|
||||
)
|
||||
}
|
||||
|
||||
func oneStorageAuthToLoginState(in models.AuthenticationMethodType) LoginNextState {
|
||||
switch in {
|
||||
case models.AuthMethodGAuth:
|
||||
return LoginNext2FaTotp
|
||||
case models.AuthMethodMail:
|
||||
return LoginNext2FaMail
|
||||
case models.AuthMethodPasskey:
|
||||
return LoginStartPasskey
|
||||
case models.AuthMethodPasskey2fa:
|
||||
return LoginNext2FaPasskey
|
||||
case models.AuthMethodPassword:
|
||||
return LoginStartPassword
|
||||
default:
|
||||
return LoginUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a given username can log in.
|
||||
// It only provides a yes/no answer and an error in case some check failed
|
||||
// due to unforseen circumstances. Though the error will always be ErrCantLogin
|
||||
// in case the known information (excluding database or other failures) prevents
|
||||
// a login
|
||||
//
|
||||
// TODO: Decide whether to include the reason for disallowed login
|
||||
func (a *Authenticator) canUsernameLogin(username string) (bool, error) {
|
||||
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !acc.FinishedRegistration {
|
||||
return false, ErrCantLogin
|
||||
}
|
||||
|
||||
finalRole := storage.CollapseRolesIntoOne(
|
||||
sliceutils.Map(acc.Roles, func(t models.UserToRole) models.Role {
|
||||
return t.Role
|
||||
})...)
|
||||
if finalRole.CanLogin != nil && !*finalRole.CanLogin {
|
||||
return false, ErrCantLogin
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue