✨ An entire complete sign in user flow
This commit is contained in:
@ -25,7 +25,7 @@ Thank you for your cooperation in helping us maintain the security of your accou
|
||||
Best regards,
|
||||
%s`
|
||||
|
||||
func GetPasswordFactor(userId uint) (models.AuthFactor, error) {
|
||||
func GetPasswordTypeFactor(userId uint) (models.AuthFactor, error) {
|
||||
var factor models.AuthFactor
|
||||
err := database.C.Where(models.AuthFactor{
|
||||
Type: models.PasswordAuthFactor,
|
||||
@ -53,6 +53,15 @@ func ListUserFactor(userId uint) ([]models.AuthFactor, error) {
|
||||
return factors, err
|
||||
}
|
||||
|
||||
func CountUserFactor(userId uint) int64 {
|
||||
var count int64
|
||||
database.C.Where(models.AuthFactor{
|
||||
AccountID: userId,
|
||||
}).Model(&models.AuthFactor{}).Count(&count)
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func GetFactorCode(factor models.AuthFactor) (bool, error) {
|
||||
switch factor.Type {
|
||||
case models.EmailPasswordFactor:
|
||||
|
18
pkg/services/mfa.go
Normal file
18
pkg/services/mfa.go
Normal file
@ -0,0 +1,18 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/models"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
)
|
||||
|
||||
func GetFactorName(w models.AuthFactorType, localizer *i18n.Localizer) string {
|
||||
unknown, _ := localizer.LocalizeMessage(&i18n.Message{ID: "unknown"})
|
||||
mfaEmail, _ := localizer.LocalizeMessage(&i18n.Message{ID: "mfaFactorEmail"})
|
||||
|
||||
switch w {
|
||||
case models.EmailPasswordFactor:
|
||||
return mfaEmail
|
||||
default:
|
||||
return unknown
|
||||
}
|
||||
}
|
@ -27,18 +27,23 @@ func DetectRisk(user models.Account, ip, ua string) bool {
|
||||
|
||||
func NewTicket(user models.Account, ip, ua string) (models.AuthTicket, error) {
|
||||
var ticket models.AuthTicket
|
||||
if err := database.C.Where(models.AuthTicket{
|
||||
AccountID: user.ID,
|
||||
}).First(&ticket).Error; err == nil {
|
||||
if err := database.C.
|
||||
Where("account_id = ? AND expired_at < ? AND available_at IS NULL", time.Now(), user.ID).
|
||||
First(&ticket).Error; err == nil {
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
requireMFA := DetectRisk(user, ip, ua)
|
||||
if count := CountUserFactor(user.ID); count <= 1 {
|
||||
requireMFA = false
|
||||
}
|
||||
|
||||
ticket = models.AuthTicket{
|
||||
Claims: []string{"*"},
|
||||
Audiences: []string{"passport"},
|
||||
IpAddress: ip,
|
||||
UserAgent: ua,
|
||||
RequireMFA: DetectRisk(user, ip, ua),
|
||||
RequireMFA: requireMFA,
|
||||
RequireAuthenticate: true,
|
||||
ExpiredAt: lo.ToPtr(time.Now().Add(2 * time.Hour)),
|
||||
AvailableAt: nil,
|
||||
@ -85,16 +90,19 @@ func ActiveTicketWithPassword(ticket models.AuthTicket, password string) (models
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
if factor, err := GetPasswordFactor(ticket.AccountID); err != nil {
|
||||
if factor, err := GetPasswordTypeFactor(ticket.AccountID); err != nil {
|
||||
return ticket, fmt.Errorf("unable to active ticket: %v", err)
|
||||
} else if err = CheckFactor(factor, password); err != nil {
|
||||
return ticket, err
|
||||
}
|
||||
|
||||
ticket.AvailableAt = lo.ToPtr(time.Now())
|
||||
ticket.RequireAuthenticate = false
|
||||
|
||||
if !ticket.RequireAuthenticate && !ticket.RequireMFA {
|
||||
ticket.AvailableAt = lo.ToPtr(time.Now())
|
||||
ticket.GrantToken = lo.ToPtr(uuid.NewString())
|
||||
ticket.AccessToken = lo.ToPtr(uuid.NewString())
|
||||
ticket.RefreshToken = lo.ToPtr(uuid.NewString())
|
||||
}
|
||||
|
||||
if err := database.C.Save(&ticket).Error; err != nil {
|
||||
@ -119,6 +127,9 @@ func ActiveTicketWithMFA(ticket models.AuthTicket, factor models.AuthFactor, cod
|
||||
|
||||
if !ticket.RequireAuthenticate && !ticket.RequireMFA {
|
||||
ticket.AvailableAt = lo.ToPtr(time.Now())
|
||||
ticket.GrantToken = lo.ToPtr(uuid.NewString())
|
||||
ticket.AccessToken = lo.ToPtr(uuid.NewString())
|
||||
ticket.RefreshToken = lo.ToPtr(uuid.NewString())
|
||||
}
|
||||
|
||||
if err := database.C.Save(&ticket).Error; err != nil {
|
||||
@ -128,10 +139,10 @@ func ActiveTicketWithMFA(ticket models.AuthTicket, factor models.AuthFactor, cod
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
func RegenSession(session models.AuthTicket) (models.AuthTicket, error) {
|
||||
session.GrantToken = lo.ToPtr(uuid.NewString())
|
||||
session.AccessToken = lo.ToPtr(uuid.NewString())
|
||||
session.RefreshToken = lo.ToPtr(uuid.NewString())
|
||||
err := database.C.Save(&session).Error
|
||||
return session, err
|
||||
func RegenSession(ticket models.AuthTicket) (models.AuthTicket, error) {
|
||||
ticket.GrantToken = lo.ToPtr(uuid.NewString())
|
||||
ticket.AccessToken = lo.ToPtr(uuid.NewString())
|
||||
ticket.RefreshToken = lo.ToPtr(uuid.NewString())
|
||||
err := database.C.Save(&ticket).Error
|
||||
return ticket, err
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ func GetToken(ticket models.AuthTicket) (string, string, error) {
|
||||
return refresh, access, fmt.Errorf("unable to encode token, access or refresh token id missing")
|
||||
}
|
||||
|
||||
accessDuration := time.Duration(viper.GetInt64("access_token_duration")) * time.Second
|
||||
refreshDuration := time.Duration(viper.GetInt64("refresh_token_duration")) * time.Second
|
||||
accessDuration := time.Duration(viper.GetInt64("security.access_token_duration")) * time.Second
|
||||
refreshDuration := time.Duration(viper.GetInt64("security.refresh_token_duration")) * time.Second
|
||||
|
||||
var err error
|
||||
sub := strconv.Itoa(int(ticket.AccountID))
|
||||
|
Reference in New Issue
Block a user