Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
package model
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// UserVerificationState is an authoritative, credential-free projection for
|
|
// choosing an authentication method. Loading it never fetches credential secrets.
|
|
type UserVerificationState struct {
|
|
UserID int
|
|
Status int
|
|
Role int
|
|
AuthVersion int64
|
|
HasPassword bool
|
|
HasTwoFA bool
|
|
TwoFALocked bool
|
|
HasPasskey bool
|
|
}
|
|
|
|
func GetUserVerificationState(userID int) (*UserVerificationState, error) {
|
|
return getUserVerificationState(DB, userID, false)
|
|
}
|
|
|
|
func getUserVerificationState(tx *gorm.DB, userID int, forUpdate bool) (*UserVerificationState, error) {
|
|
if userID <= 0 {
|
|
return nil, ErrUserSessionInvalid
|
|
}
|
|
var state UserVerificationState
|
|
query := tx.Model(&User{}).Select(
|
|
"id AS user_id, status, role, auth_version, CASE WHEN password <> '' THEN 1 ELSE 0 END AS has_password, "+
|
|
"EXISTS (?) AS has_two_fa, EXISTS (?) AS two_fa_locked, EXISTS (?) AS has_passkey",
|
|
tx.Model(&TwoFA{}).Select("1").Where("user_id = ? AND is_enabled = ?", userID, true),
|
|
tx.Model(&TwoFA{}).Select("1").Where("user_id = ? AND is_enabled = ? AND locked_until > ?", userID, true, time.Now()),
|
|
tx.Model(&PasskeyCredential{}).Select("1").Where("user_id = ?", userID),
|
|
).Where("id = ?", userID)
|
|
if forUpdate {
|
|
query = lockForUpdate(query)
|
|
}
|
|
if err := query.Take(&state).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &state, nil
|
|
}
|
|
|
|
// CreateUserSessionFromLoginFlow commits the one-time login authorization and
|
|
// the resulting session together. The user lock serializes credential changes
|
|
// and session issuance, including the per-user session limits. validate runs
|
|
// inside the transaction and may write through tx.
|
|
func CreateUserSessionFromLoginFlow(token string, session *UserSession, validate func(*gorm.DB, *AuthFlow, *UserVerificationState) error) error {
|
|
if session == nil || validate == nil {
|
|
return ErrUserSessionInvalid
|
|
}
|
|
cacheDeadline := userSessionCacheDeadline()
|
|
_, err := ConsumeAuthFlowWithAction(token, AuthFlowMatch{
|
|
Purpose: AuthFlowPurposeLoginVerification, UserId: session.UserID,
|
|
}, func(tx *gorm.DB, flow *AuthFlow) error {
|
|
state, err := getUserVerificationState(tx, session.UserID, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state.Status != common.UserStatusEnabled || state.AuthVersion != session.UserAuthVersion {
|
|
return ErrUserSessionInactive
|
|
}
|
|
if err := validate(tx, flow, state); err != nil {
|
|
return err
|
|
}
|
|
now := time.Now().Unix()
|
|
var activeCount, issuanceCount int64
|
|
if err := tx.Model(&UserSession{}).Where("user_id = ? AND status = ? AND expires_at > ?", session.UserID, UserSessionStatusActive, now).Count(&activeCount).Error; err != nil {
|
|
return err
|
|
}
|
|
if activeCount >= int64(common.UserSessionActiveLimit) {
|
|
return ErrUserSessionLimit
|
|
}
|
|
if err := tx.Model(&UserSession{}).Where("user_id = ? AND created_at > ?", session.UserID, now-common.UserSessionIssuanceWindowSeconds).Count(&issuanceCount).Error; err != nil {
|
|
return err
|
|
}
|
|
if issuanceCount >= int64(common.UserSessionIssuanceLimit) {
|
|
return ErrUserSessionIssuanceLimit
|
|
}
|
|
return createUserSessionWithTx(tx, session)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return publishCreatedUserSession(session, cacheDeadline)
|
|
}
|