This commit is contained in:
parent
7ae75caaf5
commit
e7e48bfd51
2 changed files with 76 additions and 75 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue