Move helpers in auth.go to helpers.go
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
Melody Becker 2025-04-01 11:00:36 +02:00
parent 7ae75caaf5
commit e7e48bfd51
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
2 changed files with 76 additions and 75 deletions

View file

@ -10,12 +10,7 @@ import (
"time" "time"
"git.mstar.dev/mstar/goutils/other" "git.mstar.dev/mstar/goutils/other"
"git.mstar.dev/mstar/goutils/sliceutils"
"github.com/go-webauthn/webauthn/webauthn" "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 { type Authenticator struct {
@ -45,73 +40,3 @@ func New(webauthnConfig *webauthn.Config) (*Authenticator, error) {
recentlyUsedTotpTokens: make(map[string]time.Time), recentlyUsedTotpTokens: make(map[string]time.Time),
}, nil }, 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
}

View file

@ -5,8 +5,14 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"time"
"git.mstar.dev/mstar/goutils/sliceutils"
"golang.org/x/crypto/argon2" "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 // 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) key := argon2.IDKey(password, salt, 1, 64*1024, 4, 32)
return key, salt, nil 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
}