Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/dto/auth_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type SessionResponse struct {
type MFAEnableResponse struct {
Secret string `json:"secret"`
QRCodeURL string `json:"qrCodeUrl"`
BackupCodes []string `json:"backupCodes,omitempty"`
}

// MFAVerifyRequest represents the request to verify/enable MFA
Expand All @@ -106,6 +107,7 @@ type MFADisableRequest struct {
type MFALoginRequest struct {
MFAToken string `json:"mfaToken" binding:"required"`
Code string `json:"code" binding:"required,len=6"`
BackupCode string `json:"backupCode,omitempty"`
Comment on lines 107 to +110

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how LoginMFA binds and forwards fields today.
rg -n --type=go 'type MFALoginRequest|LoginMFA\(|ShouldBindJSON|VerifyLoginMFA\(' internal/dto internal/handler internal/service

Repository: roshankumar0036singh/auth-server

Length of output: 2119


🏁 Script executed:

#!/bin/bash
# Read the MFALoginRequest struct definition
sed -n '100,115p' internal/dto/auth_dto.go

Repository: roshankumar0036singh/auth-server

Length of output: 835


🏁 Script executed:

#!/bin/bash
# Read the LoginMFA handler and its implementation
sed -n '969,990p' internal/handler/auth_handler.go

Repository: roshankumar0036singh/auth-server

Length of output: 726


🏁 Script executed:

#!/bin/bash
# Read the VerifyLoginMFA service method signature and initial implementation
sed -n '426,460p' internal/service/auth_service.go

Repository: roshankumar0036singh/auth-server

Length of output: 1304


🏁 Script executed:

#!/bin/bash
# Read more of the VerifyLoginMFA service method
sed -n '426,500p' internal/service/auth_service.go

Repository: roshankumar0036singh/auth-server

Length of output: 2355


Code field blocks backup-code-only authentication requests.

The Code field has binding:"required", so requests with only backupCode cannot pass JSON validation. Additionally, the handler doesn't pass req.BackupCode to VerifyLoginMFA, and the service signature doesn't accept a backup code parameter—currently, backup codes are only checked as a fallback when TOTP validation fails.

To support backup-code-only authentication as an alternative MFA method:

Required changes
 type MFALoginRequest struct {
 	MFAToken string `json:"mfaToken" binding:"required"`
-	Code     string `json:"code" binding:"required,len=6"`
+	Code     string `json:"code,omitempty" binding:"omitempty,len=6"`
 	BackupCode string `json:"backupCode,omitempty"`
 }

Then update the handler to pass req.BackupCode to the service, and update VerifyLoginMFA to accept and handle the backup code parameter, enforcing that exactly one of code or backupCode is provided.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/dto/auth_dto.go` around lines 107 - 110, The MFALoginRequest struct
requires the Code field with binding:"required", which prevents requests with
only a backup code from passing validation. To support backup-code-only
authentication, remove the required constraint from the Code field in the
MFALoginRequest struct so both Code and BackupCode can be optional. Then update
the handler that processes MFALoginRequest to pass req.BackupCode when calling
VerifyLoginMFA. Update the VerifyLoginMFA service method signature to accept the
backup code parameter. Finally, implement validation logic in the service to
ensure exactly one of code or backupCode is provided and handle both
authentication paths (TOTP and backup code) accordingly.

}

// LoginResponse represents the login response (updated for MFA)
Expand Down
1 change: 1 addition & 0 deletions internal/handler/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,7 @@ func (h *AuthHandler) VerifyMFA(c *gin.Context) {
c.JSON(http.StatusBadRequest, utils.ErrorResponse("MFA verification failed", err))
return
}


c.JSON(http.StatusOK, utils.SuccessResponse("MFA enabled successfully", nil))
}
Expand Down
28 changes: 28 additions & 0 deletions internal/models/backup_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package models

import (
"time"

"github.com/google/uuid"
"gorm.io/gorm"
)

type BackupCode struct {
ID string `gorm:"type:uuid;primaryKey"`
UserID string `gorm:"type:uuid;not null;index"`
CodeHash string `gorm:"not null"`
Used bool `gorm:"default:false"`
CreatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}

func (b *BackupCode) BeforeCreate(tx *gorm.DB) error {
if b.ID == "" {
b.ID = uuid.New().String()
}
return nil
}

func (BackupCode) TableName() string {
return "backup_codes"
Comment on lines +10 to +27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether generated API docs include the new backup-code fields/endpoint contract.
fd -i 'swagger|openapi|docs' .
rg -n --hidden -S 'backupCode|backupCodes|/api/auth/login/mfa' docs internal || true

Repository: roshankumar0036singh/auth-server

Length of output: 1766


🏁 Script executed:

# Check the MFA handler for Swagger annotations
sed -n '900,1050p' internal/handler/auth_handler.go

Repository: roshankumar0036singh/auth-server

Length of output: 2967


🏁 Script executed:

# Check if BackupCode model is documented in Swagger schema
rg -A 20 '"BackupCode"' docs/swagger.json || true

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check the MFA response structure in Swagger
rg -B 5 -A 15 'backupCodes' docs/swagger.json || true

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check the MFALoginResponse DTO structure
rg -A 10 'type MFALoginResponse' internal/dto/auth_dto.go

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check what fields are in the MFA response DTO
sed -n '80,120p' internal/dto/auth_dto.go

Repository: roshankumar0036singh/auth-server

Length of output: 1497


🏁 Script executed:

# Check the complete LoginResponse DTO structure
rg -A 20 'type LoginResponse struct' internal/dto/auth_dto.go

Repository: roshankumar0036singh/auth-server

Length of output: 1029


🏁 Script executed:

# Check if there's a specific MFA login response DTO
rg -B 2 -A 10 'MFALogin.*Response' internal/dto/auth_dto.go

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check if BackupCode model is documented in the generated Swagger schema
cat docs/swagger.json | grep -i 'backupcode\|backup_code' | head -20

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check the actual LoginResponse definition in swagger.json
rg -B 3 -A 15 'LoginResponse' docs/swagger.json | head -50

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check handler annotation more broadly around the MFA endpoints
rg -B 5 'LoginMFA' internal/handler/auth_handler.go | grep -E '@|func'

Repository: roshankumar0036singh/auth-server

Length of output: 395


🏁 Script executed:

# Check what utils.Response structure looks like and how it's used in Swagger
rg -A 5 'type Response struct' internal/utils/ || rg -A 5 'type Response struct' . --type go | head -20

Repository: roshankumar0036singh/auth-server

Length of output: 538


🏁 Script executed:

# Check if there are other handlers that document specific response structures
rg '`@Success`.*\{object\}' internal/handler/auth_handler.go | head -10

Repository: roshankumar0036singh/auth-server

Length of output: 570


🏁 Script executed:

# Verify if LoginResponse should have BackupCodes field
rg -B 5 -A 15 'VerifyLoginMFA' internal/service/auth_service.go | head -50

Repository: roshankumar0036singh/auth-server

Length of output: 1135


🏁 Script executed:

# Check when/how backup codes are returned - in enable MFA or login?
rg -B 5 -A 15 'BackupCode.*Create\|Generate.*Backup' internal/service/auth_service.go | head -60

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check the MFA enable endpoint handler to see if it documents BackupCodes
rg -B 8 'EnableMFA' internal/handler/auth_handler.go | grep -E '@|func' | head -20

Repository: roshankumar0036singh/auth-server

Length of output: 377


🏁 Script executed:

# Check if docs need to include BackupCode model schema
rg '"BackupCode"' docs/swagger.yaml

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check if MFAEnableResponse is referenced in any Swagger annotation
rg 'MFAEnableResponse|MFALoginResponse' internal/handler/auth_handler.go

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Let's see the full EnableMFA and GenerateBackupCodes handlers
sed -n '850,950p' internal/handler/auth_handler.go

Repository: roshankumar0036singh/auth-server

Length of output: 3321


🏁 Script executed:

# Check the service method to confirm what it returns
rg -B 3 -A 20 'func.*GenerateBackupCodes' internal/service/auth_service.go

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check what EnableMFA service returns
rg -B 2 -A 20 'func.*EnableMFA' internal/service/auth_service.go | head -40

Repository: roshankumar0036singh/auth-server

Length of output: 1137


🏁 Script executed:

# Check if swagger.json has any response definitions for MFA endpoints
rg -A 30 '"/api/auth/mfa/enable"' docs/swagger.json

Repository: roshankumar0036singh/auth-server

Length of output: 1050


🏁 Script executed:

# Confirm the actual return type of EnableMFA
ast-grep outline internal/service/auth_service.go --match 'EnableMFA' --view expanded

Repository: roshankumar0036singh/auth-server

Length of output: 361


🏁 Script executed:

# Check if BackupCode model has any Swagger annotations
cat internal/models/backup_code.go

Repository: roshankumar0036singh/auth-server

Length of output: 730


🏁 Script executed:

# Verify the complete definitions section in swagger.json
rg '"definitions":|"components":' docs/swagger.json -A 5

Repository: roshankumar0036singh/auth-server

Length of output: 351


🏁 Script executed:

# Check if there are Swagger struct tags on BackupCode model
sed -n '1,50p' internal/models/backup_code.go

Repository: roshankumar0036singh/auth-server

Length of output: 730


🏁 Script executed:

# Check if other models have Swagger documentation/struct tags
rg 'swagger:' internal/models/ | head -10

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check if MFAEnableResponse is in the swagger definitions
rg '"MFAEnableResponse"' docs/swagger.json

Repository: roshankumar0036singh/auth-server

Length of output: 170


🏁 Script executed:

# Check what the Makefile target 'swagger' does
cat Makefile | grep -A 10 'swagger'

Repository: roshankumar0036singh/auth-server

Length of output: 572


Update Swagger annotations in MFA handlers to reference specific response DTOs, then run make swagger to regenerate docs.

The EnableMFA handler returns dto.MFAEnableResponse (which includes BackupCodes), but its Swagger annotation uses the generic @Success 200 {object} utils.Response. Update the annotation to reference the specific DTO so the generated docs properly document the backup-code response structure. Similarly, review other MFA endpoint annotations (LoginMFA, VerifyMFA, DisableMFA) to ensure they document specific response types rather than generic utils.Response. After updating annotations, run make swagger to regenerate the API documentation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/models/backup_code.go` around lines 10 - 27, The Swagger annotations
for MFA handler functions (EnableMFA, LoginMFA, VerifyMFA, DisableMFA) are using
the generic utils.Response type instead of documenting the specific response
DTOs they return. Update each handler's `@Success` annotation to reference the
correct specific DTO type it returns (for example, EnableMFA should reference
dto.MFAEnableResponse instead of utils.Response). After updating all the
annotations for these handlers, run make swagger to regenerate the API
documentation.

Source: Coding guidelines

}
38 changes: 38 additions & 0 deletions internal/repository/backup_code_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package repository

import (
"errors"

"github.com/roshankumar0036singh/auth-server/internal/models"
"gorm.io/gorm"
)

var ErrBackupCodeNotFound = errors.New("backup code not found")

type BackupCodeRepository struct {
db *gorm.DB
}

func NewBackupCodeRepository(db *gorm.DB) *BackupCodeRepository {
return &BackupCodeRepository{db: db}
}

func (r *BackupCodeRepository) Create(code *models.BackupCode) error {
return r.db.Create(code).Error
}

func (r *BackupCodeRepository) FindByUserID(userID string) ([]models.BackupCode, error) {
var codes []models.BackupCode

err := r.db.
Where("user_id = ? AND used = ?", userID, false).
Find(&codes).Error

return codes, err
}

func (r *BackupCodeRepository) MarkUsed(id string) error {
return r.db.Model(&models.BackupCode{}).
Where("id = ?", id).
Update("used", true).Error
Comment on lines +34 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify MarkUsed behavior and all call sites.
rg -n --type=go 'func \(r \*BackupCodeRepository\) MarkUsed|ErrBackupCodeNotFound|MarkUsed\(' internal/repository internal/service

Repository: roshankumar0036singh/auth-server

Length of output: 469


🏁 Script executed:

#!/bin/bash
# Get the full BackupCodeRepository implementation
cat -n internal/repository/backup_code_repository.go

Repository: roshankumar0036singh/auth-server

Length of output: 1273


🏁 Script executed:

#!/bin/bash
# Check the call site in auth_service.go around line 465
sed -n '450,480p' internal/service/auth_service.go | cat -n

Repository: roshankumar0036singh/auth-server

Length of output: 1096


🏁 Script executed:

#!/bin/bash
# Check the BackupCode model structure
find . -name "*.go" -type f | xargs grep -l "type BackupCode" | head -5

Repository: roshankumar0036singh/auth-server

Length of output: 251


🏁 Script executed:

#!/bin/bash
# Check the BackupCode model structure
cat -n internal/models/backup_code.go

Repository: roshankumar0036singh/auth-server

Length of output: 926


🏁 Script executed:

#!/bin/bash
# Get more context around the call site to understand the full flow
sed -n '440,490p' internal/service/auth_service.go | cat -n

Repository: roshankumar0036singh/auth-server

Length of output: 1840


Make backup-code consumption atomic in MarkUsed.

Concurrent requests can both pass verification and reuse a one-time backup code. The current implementation updates by id only without checking the used field, allowing race conditions. Additionally, ErrBackupCodeNotFound is never returned because RowsAffected is not verified.

Add an AND used = ? condition to the WHERE clause and check RowsAffected to ensure the operation is atomic:

Suggested fix
 func (r *BackupCodeRepository) MarkUsed(id string) error {
-	return r.db.Model(&models.BackupCode{}).
-		Where("id = ?", id).
-		Update("used", true).Error
+	tx := r.db.Model(&models.BackupCode{}).
+		Where("id = ? AND used = ?", id, false).
+		Update("used", true)
+	if tx.Error != nil {
+		return tx.Error
+	}
+	if tx.RowsAffected == 0 {
+		return ErrBackupCodeNotFound
+	}
+	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *BackupCodeRepository) MarkUsed(id string) error {
return r.db.Model(&models.BackupCode{}).
Where("id = ?", id).
Update("used", true).Error
func (r *BackupCodeRepository) MarkUsed(id string) error {
tx := r.db.Model(&models.BackupCode{}).
Where("id = ? AND used = ?", id, false).
Update("used", true)
if tx.Error != nil {
return tx.Error
}
if tx.RowsAffected == 0 {
return ErrBackupCodeNotFound
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/repository/backup_code_repository.go` around lines 34 - 37, The
MarkUsed method in BackupCodeRepository has a race condition vulnerability where
concurrent requests can reuse the same one-time backup code. Modify the WHERE
clause in the Update call to add an AND condition checking that the used field
is false before updating it to true, making the operation atomic. Additionally,
after the Update call, check the RowsAffected value to verify that exactly one
row was actually updated, and return ErrBackupCodeNotFound if no rows were
affected (indicating the code was already used or doesn't exist).

}
1 change: 1 addition & 0 deletions internal/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func SetupRoutes(router *gin.Engine, db *gorm.DB, redisClient *redis.Client, cfg
auditService,
mfaService,
cfg,
backupCodeRepo *repository.BackupCodeRepository
)

// OAuth Provider service
Expand Down
54 changes: 49 additions & 5 deletions internal/service/auth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
auditService *AuditService
mfaService *MFAService
config *config.Config
backupCodeRepo *repository.BackupCodeRepository
}

func NewAuthService(
Expand All @@ -61,6 +62,7 @@
auditService *AuditService,
mfaService *MFAService,
cfg *config.Config,
backupCodeRepo *repository.BackupCodeRepository,
) *AuthService {
return &AuthService{
userRepo: userRepo,
Expand All @@ -73,6 +75,7 @@
auditService: auditService,
mfaService: mfaService,
config: cfg,
backupCodeRepo: backupCodeRepo,
}
}
func (s *AuthService) getRefreshTokenExpiry() time.Duration {
Expand Down Expand Up @@ -346,6 +349,27 @@
if !s.mfaService.ValidateMFA(user.MFASecret, code) {
return ErrInvalidMFACode
}
for i := 0; i < 10; i++ {
code := s.tokenService.GenerateRandomString(8)

hash, err := bcrypt.GenerateFromPassword(
[]byte(code),
bcrypt.DefaultCost,
)
if err != nil {
return err
}

backup := &models.BackupCode{
UserID: userID,
CodeHash: string(hash),
Used: false,
}

if err := s.backupCodeRepo.Create(backup); err != nil {
return err
}
}
Comment on lines +352 to +372

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Generated backup codes are discarded instead of being returned once.

Line 352-Line 372 creates plaintext backup codes and only stores hashes, but this method returns error only. That leaves no path to deliver the one-time backup codes to the user at enable time.

Suggested direction
-func (s *AuthService) VerifyEnableMFA(userID, code string) error {
+func (s *AuthService) VerifyEnableMFA(userID, code string) ([]string, error) {
+	backupCodes := make([]string, 0, 10)
 	...
 	for i := 0; i < 10; i++ {
 		code := s.tokenService.GenerateRandomString(8)
+		backupCodes = append(backupCodes, code)
 		...
-			return err
+			return nil, err
 		}
 	}
 	...
-	return nil
+	return backupCodes, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/auth_service.go` around lines 352 - 372, The backup code
generation loop creates plaintext codes but only stores their hashes to the
database, leaving no way to return the plaintext codes to the caller. Modify the
method signature to return a slice of strings containing the plaintext codes in
addition to the error. Then, within the loop where GenerateRandomString
generates each code and before it is hashed, append the plaintext code to a
slice that gets returned at the end of the method so users receive their
one-time backup codes.


// Enable MFA
if err := s.userRepo.Update(userID, map[string]interface{}{
Expand Down Expand Up @@ -399,7 +423,7 @@
// short-lived MFA-pending token issued by the password step (Login), so the
// password cannot be bypassed, and rate-limits code attempts to prevent
// brute-forcing the 6-digit TOTP.
func (s *AuthService) VerifyLoginMFA(mfaToken, code, ipAddress, userAgent string) (*dto.LoginResponse, error) {

Check failure on line 426 in internal/service/auth_service.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=roshankumar0036singh_auth-server&issues=AZ70YghutPsGjMm8YpYy&open=AZ70YghutPsGjMm8YpYy&pullRequest=219
ctx := context.Background()

userID, err := s.tokenService.ValidateMFAToken(mfaToken)
Expand All @@ -425,13 +449,33 @@
return nil, errors.New("MFA not enabled for this user")
}

if !s.mfaService.ValidateMFA(user.MFASecret, code) {
s.cacheService.IncrementMFAAttempts(ctx, userID)
if err := s.auditService.LogEvent(&user.ID, "MFA_LOGIN_FAILED", "USER", user.ID, ipAddress, userAgent, nil); err != nil {
log.Printf("failed to write MFA_LOGIN_FAILED audit log for user %s: %v", user.ID, err)
valid := s.mfaService.ValidateMFA(user.MFASecret, code)

if !valid {
codes, err := s.backupCodeRepo.FindByUserID(userID)

if err == nil {
for _, bc := range codes {
if bcrypt.CompareHashAndPassword(
[]byte(bc.CodeHash),
[]byte(code),
) == nil {

valid = true
_ = s.backupCodeRepo.MarkUsed(bc.ID)
break
Comment on lines +454 to +466

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Only mark MFA valid after backup-code consumption succeeds.

Line 464 sets valid = true before persistence is confirmed, and Line 465 drops MarkUsed errors. A DB write failure would allow login without consuming the one-time code.

Suggested fix
-				valid = true
-				_ = s.backupCodeRepo.MarkUsed(bc.ID)
-				break
+				if err := s.backupCodeRepo.MarkUsed(bc.ID); err == nil {
+					valid = true
+					break
+				}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/auth_service.go` around lines 454 - 466, The code sets valid
to true before confirming that the backup code has been successfully marked as
used in the database. In the backup code validation block within the for loop,
replace the line that ignores the MarkUsed error with `_ =
s.backupCodeRepo.MarkUsed(bc.ID)` by capturing the error returned from
s.backupCodeRepo.MarkUsed(bc.ID). Only set valid to true after confirming that
MarkUsed succeeded, and handle any persistence error appropriately. This ensures
that a database write failure does not result in marking MFA as valid without
actually consuming the one-time backup code.

}
}
return nil, ErrInvalidMFACode
}
}

if !valid {
s.cacheService.IncrementMFAAttempts(ctx, userID)
if err := s.auditService.LogEvent(&user.ID, "MFA_LOGIN_FAILED", "USER", user.ID, ipAddress, userAgent, nil); err != nil {
log.Printf("failed to write MFA_LOGIN_FAILED audit log for user %s: %v", user.ID, err)
}
return nil, ErrInvalidMFACode
}

s.cacheService.ResetMFAAttempts(ctx, userID)

Expand Down
6 changes: 6 additions & 0 deletions internal/testutils/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ func SetupIntegrationTest(t *testing.T) (*service.AuthService, *gorm.DB, *minire
&models.PasswordResetToken{},
&models.AuditLog{},
&models.OAuthAccessToken{},
&models.BackupCode{},
)



assert.NoError(t, err)
assert.NoError(t, db.Exec("DELETE FROM oauth_access_tokens").Error)

Expand Down Expand Up @@ -117,6 +121,7 @@ func SetupIntegrationTest(t *testing.T) (*service.AuthService, *gorm.DB, *minire
verificationRepo := repository.NewVerificationRepository(db)
passwordResetRepo := repository.NewPasswordResetRepository(db)
auditRepo := repository.NewAuditRepository(db)
backupCodeRepo := repository.NewBackupCodeRepository(db)

// 4. Services
cfg := &config.Config{
Expand All @@ -141,6 +146,7 @@ func SetupIntegrationTest(t *testing.T) (*service.AuthService, *gorm.DB, *minire
auditService,
mfaService,
cfg,
backupCodeRepo,
)

return authService, db, mr
Expand Down
Loading