diff --git a/auth-new/auth.go b/auth-new/auth.go index 12a7257..eb23074 100644 --- a/auth-new/auth.go +++ b/auth-new/auth.go @@ -10,12 +10,7 @@ import ( "time" "git.mstar.dev/mstar/goutils/other" - "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 { @@ -45,73 +40,3 @@ func New(webauthnConfig *webauthn.Config) (*Authenticator, error) { recentlyUsedTotpTokens: make(map[string]time.Time), }, nil } - -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 -} diff --git a/auth-new/helpers.go b/auth-new/helpers.go index 8907cdb..7600626 100644 --- a/auth-new/helpers.go +++ b/auth-new/helpers.go @@ -5,8 +5,14 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "time" + "git.mstar.dev/mstar/goutils/sliceutils" "golang.org/x/crypto/argon2" + + "git.mstar.dev/mstar/linstrom/storage" + "git.mstar.dev/mstar/linstrom/storage-new/dbgen" + "git.mstar.dev/mstar/linstrom/storage-new/models" ) // Len of salt for passwords in bytes @@ -108,3 +114,73 @@ func deriveKey(password, salt []byte) ([]byte, []byte, error) { key := argon2.IDKey(password, salt, 1, 64*1024, 4, 32) return key, salt, nil } + +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 +}