init push
This commit is contained in:
595
internal/repository/mysql/auth_repo.go
Normal file
595
internal/repository/mysql/auth_repo.go
Normal file
@@ -0,0 +1,595 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"wucher/internal/domain/auth"
|
||||
)
|
||||
|
||||
type AuthRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAuthRepository(db *gorm.DB) *AuthRepository {
|
||||
return &AuthRepository{db: db}
|
||||
}
|
||||
|
||||
// Users
|
||||
|
||||
func (r *AuthRepository) CreateUser(ctx context.Context, user *auth.User) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
|
||||
user.CreatedBy = actor
|
||||
user.UpdatedBy = actor
|
||||
}
|
||||
if err := tx.Create(user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return replaceUserRolesTx(ctx, tx, user.ID, user.RoleID, user.RoleIDs)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetUserByID(ctx context.Context, id []byte) (*auth.User, error) {
|
||||
var user auth.User
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("ProfileAttachment").
|
||||
Preload("ProfileAttachment.File").
|
||||
First(&user, "id = ?", id).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := attachAuthUserRoles(ctx, r.db, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetUserByEmail(ctx context.Context, email string) (*auth.User, error) {
|
||||
var user auth.User
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("ProfileAttachment").
|
||||
Preload("ProfileAttachment.File").
|
||||
Where("email = ?", email).
|
||||
First(&user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := attachAuthUserRoles(ctx, r.db, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetUserBySSOEmail(ctx context.Context, ssoEmail string) (*auth.User, error) {
|
||||
var user auth.User
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("ProfileAttachment").
|
||||
Preload("ProfileAttachment.File").
|
||||
Where("sso_email = ?", ssoEmail).
|
||||
First(&user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := attachAuthUserRoles(ctx, r.db, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetUserByUsername(ctx context.Context, username string) (*auth.User, error) {
|
||||
var user auth.User
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("ProfileAttachment").
|
||||
Preload("ProfileAttachment.File").
|
||||
Where("username = ?", username).
|
||||
First(&user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := attachAuthUserRoles(ctx, r.db, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) UpdateUserTimezone(ctx context.Context, id []byte, timezone string) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.User{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"timezone": timezone,
|
||||
"updated_at": time.Now().UTC(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) SetUserPassword(ctx context.Context, id []byte, passwordHash, salt []byte) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.User{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"password_hash": passwordHash,
|
||||
"salt_value": salt,
|
||||
"updated_at": time.Now().UTC(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) SetUserSecurityPIN(ctx context.Context, id []byte, pinHash []byte, setAt time.Time) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.User{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"security_pin_hash": pinHash,
|
||||
"security_pin_set_at": setAt,
|
||||
"updated_at": setAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) MarkEmailVerified(ctx context.Context, id []byte) error {
|
||||
now := time.Now().UTC()
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("email_verified_at", &now).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) CreatePasswordResetToken(ctx context.Context, token *auth.PasswordResetToken) error {
|
||||
return r.db.WithContext(ctx).Create(token).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) InvalidateActivePasswordResetTokensByUserID(ctx context.Context, userID []byte, usedAt time.Time) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.PasswordResetToken{}).
|
||||
Where("user_id = ? AND used_at IS NULL AND expires_at > ?", userID, usedAt).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) ConsumePasswordResetTokenAndSetPassword(
|
||||
ctx context.Context,
|
||||
tokenHash, passwordHash, salt []byte,
|
||||
usedAt time.Time,
|
||||
) ([]byte, bool, error) {
|
||||
var token auth.PasswordResetToken
|
||||
consumed := false
|
||||
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("token_hash = ? AND used_at IS NULL AND expires_at > ?", tokenHash, usedAt).
|
||||
First(&token).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
consumed = true
|
||||
|
||||
res := tx.Model(&auth.User{}).
|
||||
Where("id = ?", token.UserID).
|
||||
Updates(map[string]any{
|
||||
"password_hash": passwordHash,
|
||||
"salt_value": salt,
|
||||
"updated_at": usedAt,
|
||||
})
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
if err := tx.Model(&auth.PasswordResetToken{}).
|
||||
Where("id = ?", token.ID).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Model(&auth.PasswordResetToken{}).
|
||||
Where("user_id = ? AND id <> ? AND used_at IS NULL AND expires_at > ?", token.UserID, token.ID, usedAt).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
if !consumed {
|
||||
return nil, false, nil
|
||||
}
|
||||
return token.UserID, true, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) CreateSecurityPINResetToken(ctx context.Context, token *auth.SecurityPINResetToken) error {
|
||||
return r.db.WithContext(ctx).Create(token).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) InvalidateActiveSecurityPINResetTokensByUserID(ctx context.Context, userID []byte, usedAt time.Time) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.SecurityPINResetToken{}).
|
||||
Where("user_id = ? AND used_at IS NULL AND expires_at > ?", userID, usedAt).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetActiveSecurityPINResetTokenUser(ctx context.Context, tokenHash []byte, now time.Time) (*auth.User, error) {
|
||||
var user auth.User
|
||||
err := r.db.WithContext(ctx).
|
||||
Table("security_pin_reset_tokens AS token").
|
||||
Select("users.*").
|
||||
Joins("JOIN users ON users.id = token.user_id").
|
||||
Where("token.token_hash = ? AND token.used_at IS NULL AND token.expires_at > ?", tokenHash, now).
|
||||
First(&user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (r *AuthRepository) ConsumeSecurityPINResetTokenAndSetPIN(
|
||||
ctx context.Context,
|
||||
tokenHash, pinHash []byte,
|
||||
usedAt time.Time,
|
||||
) ([]byte, bool, error) {
|
||||
var token auth.SecurityPINResetToken
|
||||
consumed := false
|
||||
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("token_hash = ? AND used_at IS NULL AND expires_at > ?", tokenHash, usedAt).
|
||||
First(&token).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
consumed = true
|
||||
|
||||
res := tx.Model(&auth.User{}).
|
||||
Where("id = ?", token.UserID).
|
||||
Updates(map[string]any{
|
||||
"security_pin_hash": pinHash,
|
||||
"security_pin_set_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
})
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
if err := tx.Model(&auth.SecurityPINResetToken{}).
|
||||
Where("id = ?", token.ID).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Model(&auth.SecurityPINResetToken{}).
|
||||
Where("user_id = ? AND id <> ? AND used_at IS NULL AND expires_at > ?", token.UserID, token.ID, usedAt).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
if !consumed {
|
||||
return nil, false, nil
|
||||
}
|
||||
return token.UserID, true, nil
|
||||
}
|
||||
|
||||
// Identities (SSO)
|
||||
|
||||
func (r *AuthRepository) UpsertIdentity(ctx context.Context, identity *auth.UserIdentity) error {
|
||||
// Ensure we keep a single row per provider+subject
|
||||
return r.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "provider"}, {Name: "provider_subject"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"user_id", "email", "metadata", "updated_at", "updated_by"}),
|
||||
}).
|
||||
Create(identity).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) CreateIdentity(ctx context.Context, identity *auth.UserIdentity) error {
|
||||
return r.db.WithContext(ctx).Create(identity).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetIdentityByProviderSubject(ctx context.Context, provider, subject string) (*auth.UserIdentity, error) {
|
||||
var identity auth.UserIdentity
|
||||
tx := r.db.WithContext(ctx).
|
||||
Where("provider = ? AND provider_subject = ?", provider, subject).
|
||||
Limit(1).
|
||||
Find(&identity)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &identity, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetIdentityByUserID(ctx context.Context, userID []byte) (*auth.UserIdentity, error) {
|
||||
var identity auth.UserIdentity
|
||||
tx := r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Limit(1).
|
||||
Find(&identity)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &identity, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) DeleteIdentityByUserIDProvider(ctx context.Context, userID []byte, provider string) (bool, error) {
|
||||
tx := r.db.WithContext(ctx).
|
||||
Where("user_id = ? AND provider = ?", userID, provider).
|
||||
Delete(&auth.UserIdentity{})
|
||||
if tx.Error != nil {
|
||||
return false, tx.Error
|
||||
}
|
||||
return tx.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) UpsertMicrosoftOAuthToken(ctx context.Context, token *auth.UserMicrosoftOAuthToken) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "provider"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{
|
||||
"tenant_id",
|
||||
"microsoft_object_id",
|
||||
"microsoft_subject",
|
||||
"refresh_token_encrypted",
|
||||
"access_token_encrypted",
|
||||
"id_token_encrypted",
|
||||
"scope",
|
||||
"token_type",
|
||||
"access_token_expires_at",
|
||||
"refresh_token_expires_at",
|
||||
"refresh_token_rotated_at",
|
||||
"updated_at",
|
||||
"updated_by",
|
||||
}),
|
||||
}).
|
||||
Create(token).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) GetMicrosoftOAuthTokenByUserIDProvider(ctx context.Context, userID []byte, provider string) (*auth.UserMicrosoftOAuthToken, error) {
|
||||
var token auth.UserMicrosoftOAuthToken
|
||||
tx := r.db.WithContext(ctx).
|
||||
Where("user_id = ? AND provider = ?", userID, provider).
|
||||
Limit(1).
|
||||
Find(&token)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) DeleteMicrosoftOAuthTokenByUserIDProvider(ctx context.Context, userID []byte, provider string) (bool, error) {
|
||||
tx := r.db.WithContext(ctx).
|
||||
Where("user_id = ? AND provider = ?", userID, provider).
|
||||
Delete(&auth.UserMicrosoftOAuthToken{})
|
||||
if tx.Error != nil {
|
||||
return false, tx.Error
|
||||
}
|
||||
return tx.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// TOTP
|
||||
|
||||
func (r *AuthRepository) GetUserTOTP(ctx context.Context, userID []byte) (*auth.UserTOTP, error) {
|
||||
var totp auth.UserTOTP
|
||||
tx := r.db.WithContext(ctx).Where("user_id = ?", userID).Limit(1).Find(&totp)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &totp, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) UpsertUserTOTP(ctx context.Context, totp *auth.UserTOTP) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"secret_encrypted", "enabled", "updated_at", "updated_by"}),
|
||||
}).
|
||||
Create(totp).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) DisableUserTOTP(ctx context.Context, userID []byte) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.UserTOTP{}).
|
||||
Where("user_id = ?", userID).
|
||||
Updates(map[string]any{
|
||||
"enabled": false,
|
||||
"updated_at": time.Now().UTC(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) DeleteUserTOTPSetup(ctx context.Context, userID []byte) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Delete(&auth.UserTOTP{}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) UpdateUserTOTPLastUsed(ctx context.Context, userID []byte) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.UserTOTP{}).
|
||||
Where("user_id = ? AND enabled = 1", userID).
|
||||
Update("last_used_at", time.Now().UTC()).Error
|
||||
}
|
||||
|
||||
// Email login OTP
|
||||
|
||||
func (r *AuthRepository) GetActiveUserEmailLoginOTP(ctx context.Context, userID []byte, now time.Time) (*auth.UserEmailLoginOTP, error) {
|
||||
var row auth.UserEmailLoginOTP
|
||||
tx := r.db.WithContext(ctx).
|
||||
Where("user_id = ? AND used_at IS NULL AND expires_at > ?", userID, now.UTC()).
|
||||
Order("created_at DESC").
|
||||
Limit(1).
|
||||
Find(&row)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) CountUserEmailLoginOTPSince(ctx context.Context, userID []byte, since time.Time) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&auth.UserEmailLoginOTP{}).
|
||||
Where("user_id = ? AND created_at >= ?", userID, since.UTC()).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *AuthRepository) CreateUserEmailLoginOTP(ctx context.Context, otp *auth.UserEmailLoginOTP) error {
|
||||
return r.db.WithContext(ctx).Create(otp).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) InvalidateActiveUserEmailLoginOTPs(ctx context.Context, userID []byte, usedAt time.Time) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.UserEmailLoginOTP{}).
|
||||
Where("user_id = ? AND used_at IS NULL AND expires_at > ?", userID, usedAt.UTC()).
|
||||
Updates(map[string]any{
|
||||
"used_at": usedAt.UTC(),
|
||||
"updated_at": usedAt.UTC(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) ConsumeUserEmailLoginOTP(ctx context.Context, userID, otpHash []byte, now time.Time) (bool, int, error) {
|
||||
success := false
|
||||
attemptsLeft := 0
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var row auth.UserEmailLoginOTP
|
||||
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("user_id = ? AND used_at IS NULL AND expires_at > ?", userID, now.UTC()).
|
||||
Order("created_at DESC").
|
||||
Limit(1).
|
||||
First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(row.OTPHash) == string(otpHash) {
|
||||
success = true
|
||||
attemptsLeft = max(0, row.MaxAttempts-row.AttemptCount)
|
||||
if err := tx.Model(&auth.UserEmailLoginOTP{}).
|
||||
Where("id = ?", row.ID).
|
||||
Updates(map[string]any{
|
||||
"used_at": now.UTC(),
|
||||
"updated_at": now.UTC(),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&auth.UserEmailLoginOTP{}).
|
||||
Where("user_id = ? AND id <> ? AND used_at IS NULL AND expires_at > ?", userID, row.ID, now.UTC()).
|
||||
Updates(map[string]any{
|
||||
"used_at": now.UTC(),
|
||||
"updated_at": now.UTC(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
nextAttempts := row.AttemptCount + 1
|
||||
attemptsLeft = max(0, row.MaxAttempts-nextAttempts)
|
||||
updates := map[string]any{
|
||||
"attempt_count": nextAttempts,
|
||||
"updated_at": now.UTC(),
|
||||
}
|
||||
if nextAttempts >= row.MaxAttempts {
|
||||
updates["used_at"] = now.UTC()
|
||||
}
|
||||
return tx.Model(&auth.UserEmailLoginOTP{}).
|
||||
Where("id = ?", row.ID).
|
||||
Updates(updates).Error
|
||||
})
|
||||
return success, attemptsLeft, err
|
||||
}
|
||||
|
||||
// WebAuthn
|
||||
|
||||
func (r *AuthRepository) ListUserWebAuthnCredentials(ctx context.Context, userID []byte) ([]auth.UserWebAuthnCredential, error) {
|
||||
var credentials []auth.UserWebAuthnCredential
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Order("created_at ASC").
|
||||
Find(&credentials).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func (r *AuthRepository) CreateUserWebAuthnCredential(ctx context.Context, credential *auth.UserWebAuthnCredential) error {
|
||||
return r.db.WithContext(ctx).Create(credential).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) UpdateUserWebAuthnCredential(ctx context.Context, userID, credentialID, credentialJSON []byte, usedAt time.Time) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&auth.UserWebAuthnCredential{}).
|
||||
Where("user_id = ? AND credential_id = ?", userID, credentialID).
|
||||
Updates(map[string]any{
|
||||
"credential_json": credentialJSON,
|
||||
"last_used_at": usedAt,
|
||||
"updated_at": usedAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *AuthRepository) DeleteAllUserWebAuthnCredentials(ctx context.Context, userID []byte) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Delete(&auth.UserWebAuthnCredential{}).Error
|
||||
}
|
||||
Reference in New Issue
Block a user