package auth import ( "time" "git.mstar.dev/mstar/goutils/other" "git.mstar.dev/mstar/goutils/sliceutils" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" "gorm.io/gorm/clause" "git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/models" ) const totpUnverifiedSuffix = "-NOT_VERIFIED" func (a *Authenticator) PerformTotpLogin( username string, totpToken string, ) (LoginNextState, string, error) { if ok, err := a.canUsernameLogin(username); !ok { return 0, "", other.Error("auth", "user may not login", err) } acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First() if err != nil { return LoginNextFailure, "", other.Error("auth", "failed to find account", err) } secrets := sliceutils.Map( sliceutils.Filter(acc.AuthMethods, func(t models.UserAuthMethod) bool { return t.AuthMethod == models.AuthMethodGAuth }), func(t models.UserAuthMethod) string { return string(t.Token) }, ) found := false for _, secret := range secrets { if totp.Validate(totpToken, secret) { found = true break } } if !found { return LoginNextFailure, "", other.Error( "auth", "no fitting credential found", ErrInvalidCombination, ) } 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 } func (a *Authenticator) StartTotpRegistration( username string, tokenName string, ) (*otp.Key, error) { if ok, err := a.canUsernameLogin(username); !ok { return nil, other.Error("auth", "user may not login", err) } acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First() if err != nil { return nil, other.Error("auth", "failed to find account", err) } key, err := totp.Generate(totp.GenerateOpts{ Issuer: config.GlobalConfig.General.GetFullDomain(), AccountName: username, SecretSize: 160, }) if err != nil { return nil, err } secret := key.Secret() authToken := models.UserAuthMethod{ UserId: acc.ID, User: *acc, Token: []byte(secret), AuthMethod: models.AuthMethodGAuth, Name: tokenName + totpUnverifiedSuffix, } err = dbgen.UserAuthMethod.Create(&authToken) if err != nil { return nil, err } return key, nil }