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
158
auth-new/password.go
Normal file
158
auth-new/password.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
func hashPassword(password string) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
}
|
||||
|
||||
func comparePassword(password string, hash []byte) bool {
|
||||
return bcrypt.CompareHashAndPassword(hash, []byte(password)) == nil
|
||||
}
|
||||
|
||||
// Start a login process with a username (NOT account ID) and password
|
||||
// Returns the next state, a token corresponding to that state and error
|
||||
// Token will be empty on failure, error describes the reason for the
|
||||
// failure
|
||||
func (a *Authenticator) PerformPasswordLogin(
|
||||
username string,
|
||||
password string,
|
||||
) (nextState LoginNextState, token string, err error) {
|
||||
if ok, err := a.canUsernameLogin(username); !ok {
|
||||
return LoginNextFailure, "", other.Error("auth", "user may not login", err)
|
||||
}
|
||||
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case gorm.ErrRecordNotFound:
|
||||
return LoginNextFailure, "", ErrInvalidCombination
|
||||
}
|
||||
|
||||
var method *models.UserAuthMethod
|
||||
for _, authMethod := range acc.AuthMethods {
|
||||
if oneStorageAuthToLoginState(authMethod.AuthMethod) == LoginStartPassword {
|
||||
method = &authMethod
|
||||
break
|
||||
}
|
||||
}
|
||||
if method == nil {
|
||||
return LoginNextFailure, "", ErrUnsupportedAuthMethod
|
||||
}
|
||||
if !comparePassword(password, method.Token) {
|
||||
return LoginNextFailure, "", ErrInvalidCombination
|
||||
}
|
||||
nextStates := ConvertNewStorageAuthMethodsToLoginState(
|
||||
sliceutils.Map(
|
||||
acc.AuthMethods,
|
||||
func(t models.UserAuthMethod) models.AuthenticationMethodType {
|
||||
return t.AuthMethod
|
||||
},
|
||||
),
|
||||
false,
|
||||
)
|
||||
// Catch unknown login methods
|
||||
if nextStates&LoginUnknown == LoginUnknown {
|
||||
return LoginNextFailure, "", ErrUnknownAuthMethod
|
||||
}
|
||||
|
||||
if nextStates == LoginNextFailure {
|
||||
// Login ok, no 2fa needed
|
||||
// Create a new token. Don't generate the token itself, let postgres handle that
|
||||
token := models.AccessToken{
|
||||
User: *acc,
|
||||
UserId: acc.ID,
|
||||
ExpiresAt: time.Now().Add(time.Hour * 24 * 365),
|
||||
}
|
||||
err = dbgen.AccessToken.
|
||||
// technically, the chance of a conflict is so incredibly low that it should never ever occur
|
||||
// since both the username and a randomly generated uuid would need to be created the same. Twice
|
||||
// But, just in case, do this
|
||||
Clauses(clause.OnConflict{DoNothing: true}).
|
||||
Omit(dbgen.AccessToken.Token).
|
||||
Create(&token)
|
||||
if err != nil {
|
||||
return LoginNextFailure, "", other.Error(
|
||||
"auth",
|
||||
"failed to create new access token",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return LoginNextSucess, token.Token, nil
|
||||
}
|
||||
// TODO: Generate login process token
|
||||
loginToken := models.LoginProcessToken{
|
||||
User: *acc,
|
||||
UserId: acc.ID,
|
||||
ExpiresAt: calcAccessExpirationTimestamp(),
|
||||
Token: uuid.NewString(),
|
||||
}
|
||||
err = dbgen.LoginProcessToken.Clauses(clause.OnConflict{UpdateAll: true}).
|
||||
Create(&loginToken)
|
||||
|
||||
if err != nil {
|
||||
return LoginNextFailure, "", other.Error(
|
||||
"auth",
|
||||
"failed to create login process token",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return nextStates, loginToken.Token, nil
|
||||
}
|
||||
|
||||
// Register a password to an account
|
||||
// There is no check for whether that is allowed from the active roles.
|
||||
// If the given username already has a password set, it gets updated to the new one.
|
||||
// If there is no password set yet (i.e. during account registration or passkey only so far)
|
||||
// it creates the password link
|
||||
func (a *Authenticator) PerformPasswordRegister(username, password string) error {
|
||||
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||
if err != nil {
|
||||
return other.Error("auth", "failed to get user to add a password to", err)
|
||||
}
|
||||
passwordHash, err := hashPassword(password)
|
||||
if err != nil {
|
||||
return other.Error("auth", "failed to hash password", err)
|
||||
}
|
||||
passwordMethods := sliceutils.Filter(
|
||||
acc.AuthMethods,
|
||||
func(t models.UserAuthMethod) bool { return t.AuthMethod == models.AuthMethodPassword },
|
||||
)
|
||||
if len(passwordMethods) > 0 {
|
||||
// TODO: Decide whether PerformPasswordRegister can also update existing passwords
|
||||
// Imo yes, since an account can have at most one password set and having a separate method for updating
|
||||
// would increase complexity
|
||||
// For now, do perform an update
|
||||
dbPass := passwordMethods[0]
|
||||
_, err = dbgen.UserAuthMethod.Where(dbgen.UserAuthMethod.ID.Eq(dbPass.ID)).
|
||||
Update(dbgen.UserAuthMethod.Token, passwordHash)
|
||||
if err != nil {
|
||||
return other.Error("auth", "failed to update password", err)
|
||||
}
|
||||
} else {
|
||||
dbPass := models.UserAuthMethod{
|
||||
Token: passwordHash,
|
||||
AuthMethod: models.AuthMethodPassword,
|
||||
User: *acc,
|
||||
UserId: acc.ID,
|
||||
}
|
||||
err = dbgen.UserAuthMethod.Omit(dbgen.UserAuthMethod.ID).Create(&dbPass)
|
||||
if err != nil {
|
||||
return other.Error("auth", "failed to insert password", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue