package verification import ( "context" "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" "git.sendico.io/tech/sxchnge/bff/pkg/merrors" "git.sendico.io/tech/sxchnge/bff/pkg/model" "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" "go.uber.org/zap" ) func (db *verificationDB) validateConsumeToken( scope consumeScope, token *model.VerificationToken, ) error { if token.UsedAt != nil { db.Logger.Debug("Verification consume static check failed: token already used", scope.tokenLogFields(token)..., ) return ErorrTokenAlreadyUsed() } if !token.ExpiresAt.After(scope.now) { db.Logger.Debug("Verification consume static check failed: token expired", scope.tokenLogFields(token, zap.Time("now_utc", scope.now))..., ) return ErorrTokenExpired() } if token.MaxRetries != nil && token.Attempts >= *token.MaxRetries { db.Logger.Debug("Verification consume static check failed: attempts exceeded", scope.tokenLogFields(token)..., ) return ErrorTokenAttemptsExceeded() } db.Logger.Debug("Verification consume static checks passed", scope.tokenLogFields(token)..., ) return nil } func (db *verificationDB) consumeMatchedToken( ctx context.Context, scope consumeScope, token *model.VerificationToken, ) (*model.VerificationToken, error) { db.Logger.Debug("Verification consume atomic update attempt", scope.logFields(mzap.StorableRef(token))..., ) updated, err := db.PatchMany( ctx, scope.consumeFilter(token), repository.Patch().Set(repository.Field("usedAt"), scope.now), ) if err != nil { db.Logger.Warn("Verification consume atomic update failed", scope.logFields(mzap.StorableRef(token), zap.Error(err))..., ) return nil, err } db.Logger.Debug("Verification consume atomic update result", scope.logFields(mzap.StorableRef(token), zap.Int("updated_count", updated))..., ) if updated == 1 { token.UsedAt = &scope.now db.Logger.Debug("Verification consume succeeded", scope.tokenLogFields(token)...) return token, nil } db.incrementConsumeAttemptsAfterAtomicMiss(ctx, scope, token) return nil, db.consumeMissError(ctx, scope, token) } func (db *verificationDB) incrementConsumeAttemptsAfterAtomicMiss( ctx context.Context, scope consumeScope, token *model.VerificationToken, ) { incremented, err := db.PatchMany( ctx, repository.IDFilter(token.ID), repository.Patch().Inc(repository.Field("attempts"), 1), ) if err != nil { db.Logger.Warn("Verification consume failed to increment attempts after atomic update miss", scope.logFields(mzap.StorableRef(token), zap.Error(err))..., ) return } db.Logger.Debug("Verification consume incremented attempts after atomic update miss", scope.logFields( mzap.StorableRef(token), zap.Int("updated_count", incremented), zap.String("transaction_note", "this update occurs inside transaction and may roll back depending on returned error"), )..., ) } func (db *verificationDB) consumeMissError( ctx context.Context, scope consumeScope, token *model.VerificationToken, ) error { var fresh model.VerificationToken if err := db.FindOne(ctx, repository.IDFilter(token.ID), &fresh); err != nil { db.Logger.Warn("Verification consume failed to re-check token state", scope.logFields(mzap.StorableRef(token), zap.Error(err))..., ) return merrors.Internal("failed to re-check token state") } db.Logger.Debug("Verification consume re-checked token state after atomic miss", scope.tokenLogFields(&fresh, mzap.StorableRef(token))..., ) if fresh.UsedAt != nil { db.Logger.Debug("Verification consume final decision: already used after re-check", scope.logFields(mzap.StorableRef(token))..., ) return ErorrTokenAlreadyUsed() } if !fresh.ExpiresAt.After(scope.now) { db.Logger.Debug("Verification consume final decision: expired after re-check", scope.logFields( mzap.StorableRef(token), zap.Time("now_utc", scope.now), zap.Time("token_expires_at", fresh.ExpiresAt), )..., ) return ErorrTokenExpired() } if fresh.MaxRetries != nil && fresh.Attempts >= *fresh.MaxRetries { db.Logger.Debug("Verification consume final decision: attempts exceeded after re-check", scope.logFields( mzap.StorableRef(token), zap.Int("token_attempts", fresh.Attempts), zap.Int("token_max_retries", *fresh.MaxRetries), )..., ) return ErrorTokenAttemptsExceeded() } db.Logger.Debug("Verification consume final decision: token not found after re-check", scope.logFields(mzap.StorableRef(token))..., ) return ErorrTokenNotFound() }