Skip to content

Commit 9161761

Browse files
maxdinechclaude
andcommitted
fix(otp): use autonomous transaction for attempt tracking
The OTP attempt recording was being rolled back when verification failed because it ran in the same transaction. Now it uses a separate autonomous transaction that commits independently, ensuring attempt counts persist even when verification fails. This fixes the issue where attempt_count stayed at 0 in the database despite being incremented in the logs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 081e696 commit 9161761

File tree

1 file changed

+20
-2
lines changed

1 file changed

+20
-2
lines changed

internal/api/verify.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/supabase/auth/internal/api/apierrors"
1616
"github.com/supabase/auth/internal/api/provider"
1717
"github.com/supabase/auth/internal/api/sms_provider"
18+
"github.com/supabase/auth/internal/conf"
1819
"github.com/supabase/auth/internal/crypto"
1920
mail "github.com/supabase/auth/internal/mailer"
2021
"github.com/supabase/auth/internal/metering"
@@ -793,14 +794,16 @@ func (a *API) verifyUserAndToken(conn *storage.Connection, params *VerifyParams,
793794
isValid = isOtpValid(tokenHash, expectedToken, sentAt, config.Sms.OtpExp)
794795
}
795796

796-
// OTP Protection: Record attempt
797+
// OTP Protection: Record attempt in a separate autonomous transaction
798+
// This ensures the attempt count persists even if the main transaction rolls back
797799
if tokenType != "" {
798800
logrus.WithFields(logrus.Fields{
799801
"user_id": user.ID.String(),
800802
"token_type": tokenType,
801803
"is_valid": isValid,
802804
}).Info("OTP Protection: Recording OTP attempt")
803-
if err := recordOTPAttempt(conn, user.ID.String(), tokenType, isValid); err != nil {
805+
// Use a new connection to create an autonomous transaction
806+
if err := recordOTPAttemptAutonomous(config, user.ID.String(), tokenType, isValid); err != nil {
804807
// Log error but don't fail the request
805808
logrus.WithError(err).Warn("Failed to record OTP attempt")
806809
} else {
@@ -891,6 +894,21 @@ func checkOTPTokenInvalidated(conn *storage.Connection, userID string, tokenType
891894
return invalidatedAt != nil, nil
892895
}
893896

897+
// recordOTPAttemptAutonomous records an OTP attempt in an autonomous transaction
898+
// This ensures the attempt count persists even if the calling transaction rolls back
899+
func recordOTPAttemptAutonomous(config *conf.GlobalConfiguration, userID string, tokenType string, isValid bool) error {
900+
// Create a new database connection for autonomous transaction
901+
db, err := storage.Dial(config)
902+
if err != nil {
903+
return err
904+
}
905+
defer db.Close()
906+
907+
return db.Transaction(func(tx *storage.Connection) error {
908+
return recordOTPAttempt(tx, userID, tokenType, isValid)
909+
})
910+
}
911+
894912
// recordOTPAttempt records a failed OTP verification attempt and invalidates token after max failures
895913
func recordOTPAttempt(conn *storage.Connection, userID string, tokenType string, isValid bool) error {
896914
logrus.WithFields(logrus.Fields{

0 commit comments

Comments
 (0)