Skip to content

fix: use constant time in user lookups - #1002

Closed
steveiliop56 wants to merge 1 commit into
mainfrom
fix/auth-constant-time
Closed

fix: use constant time in user lookups#1002
steveiliop56 wants to merge 1 commit into
mainfrom
fix/auth-constant-time

Conversation

@steveiliop56

@steveiliop56 steveiliop56 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Bug Fixes
    • Improved login response timing consistency for invalid usernames and other authentication errors.
    • Helps reduce timing-based information leakage while preserving existing login auditing and error handling.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Login user lookup errors now use a constant-time helper that computes the response, pads processing to 45ms when needed, and then sends the appropriate JSON payload while preserving failure recording, auditing, and logging.

Changes

Login Error Timing

Layer / File(s) Summary
Constant-time login error handling
internal/controller/user_controller.go
Adds constantTimeRes and controller.constantTime, then routes SearchUser errors through the helper for standardized 401 and 500 responses while retaining associated logging, auditing, and attempt recording.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: making user lookup/login handling constant-time.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auth-constant-time

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/controller/user_controller.go 75.00% 7 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/controller/user_controller.go (1)

478-492: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Simplify constantTime by returning the result directly.

The current design uses a callback (rf) to process the response, which creates unnecessary inversion of control. Returning constantTimeRes directly makes the control flow more linear and easier to read. Additionally, time.Since can be used for a cleaner elapsed time calculation.

  • internal/controller/user_controller.go#L478-L492: Remove the rf callback parameter, return the result directly, and use time.Since for the elapsed time calculation.
  • internal/controller/user_controller.go#L92-L115: Capture the returned result from constantTime and call c.JSON directly instead of passing a callback.
♻️ Proposed refactor

Function definition (internal/controller/user_controller.go#L478-L492)

-func (controller *UserController) constantTime(f func() constantTimeRes, rf func(res constantTimeRes), targetTime time.Duration) {
+func (controller *UserController) constantTime(f func() constantTimeRes, targetTime time.Duration) constantTimeRes {
 	tStart := time.Now()
 	res := f()
-	tEnd := time.Now()
-	if tEnd.Sub(tStart) < targetTime {
-		time.Sleep(targetTime - tEnd.Sub(tStart))
+	if elapsed := time.Since(tStart); elapsed < targetTime {
+		time.Sleep(targetTime - elapsed)
 	}
-	rf(res)
+	return res
 }

Function caller (internal/controller/user_controller.go#L92-L115)

-		controller.constantTime(func() constantTimeRes {
+		res := controller.constantTime(func() constantTimeRes {
 			if errors.Is(err, service.ErrUserNotFound) {
 				controller.log.App.Warn().Str("username", req.Username).Msg("User not found during login attempt")
 				controller.auth.RecordLoginAttempt(req.Username, false)
 				controller.log.AuditLoginFailure(req.Username, "unknown", c.ClientIP(), "user not found")
 				return constantTimeRes{
 					Code: 401,
 					Res: gin.H{
 						"status":  401,
 						"message": "Unauthorized",
 					},
 				}
 			}
 			controller.log.App.Error().Err(err).Str("username", req.Username).Msg("Error searching for user during login attempt")
 			return constantTimeRes{
 				Code: 500,
 				Res: gin.H{
 					"status":  500,
 					"message": "Internal Server Error",
 				},
 			}
-		}, func(res constantTimeRes) {
-			c.JSON(res.Code, res.Res)
-		}, time.Millisecond*45)
+		}, time.Millisecond*45)
+		c.JSON(res.Code, res.Res)
 		return
🤖 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/controller/user_controller.go` around lines 478 - 492, Refactor
UserController.constantTime to remove the response callback, return the computed
constantTimeRes directly, and use time.Since for elapsed-time calculation while
preserving the target delay. At internal/controller/user_controller.go lines
92-115, capture the returned result from constantTime and invoke c.JSON directly
with it; update the caller and function signature consistently.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@internal/controller/user_controller.go`:
- Around line 113-115: Replace the hardcoded time.Millisecond*45 delay in the
constant-time response callback with dummy password verification using the same
work factor, or an equivalent dummy LDAP bind, when CheckUserPassword reports
ErrUserNotFound. Preserve the existing c.JSON response behavior and ensure the
missing-user path performs comparable verification work without relying on a
fixed sleep.

---

Nitpick comments:
In `@internal/controller/user_controller.go`:
- Around line 478-492: Refactor UserController.constantTime to remove the
response callback, return the computed constantTimeRes directly, and use
time.Since for elapsed-time calculation while preserving the target delay. At
internal/controller/user_controller.go lines 92-115, capture the returned result
from constantTime and invoke c.JSON directly with it; update the caller and
function signature consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fb5234f7-64dd-4758-8110-c9a84d4b4eec

📥 Commits

Reviewing files that changed from the base of the PR and between e75605b and 5c2cb08.

📒 Files selected for processing (1)
  • internal/controller/user_controller.go

Comment on lines +113 to +115
}, func(res constantTimeRes) {
c.JSON(res.Code, res.Res)
}, time.Millisecond*45)

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 | 🏗️ Heavy lift

Hardcoded timing delay is brittle against user enumeration.

While this PR aims to prevent user enumeration via a constant-time response, hardcoding 45ms is brittle. The actual time taken by CheckUserPassword (e.g., bcrypt hashing or LDAP binding) will vary significantly across different environments, hardware specifications, and server loads, or if password work-factors are updated in the future. Consequently, an attacker can still enumerate users by distinguishing between this fixed 45ms sleep and the actual, variable password verification time on the server.

To reliably mask the timing difference, consider performing a dummy password verification (e.g., computing a dummy hash with the same work factor or making a dummy LDAP bind) for the ErrUserNotFound case instead of using a hardcoded time.Sleep. If a sleep must be used, consider calibrating it dynamically based on the moving average of actual verification times.

🤖 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/controller/user_controller.go` around lines 113 - 115, Replace the
hardcoded time.Millisecond*45 delay in the constant-time response callback with
dummy password verification using the same work factor, or an equivalent dummy
LDAP bind, when CheckUserPassword reports ErrUserNotFound. Preserve the existing
c.JSON response behavior and ensure the missing-user path performs comparable
verification work without relying on a fixed sleep.

@steveiliop56

Copy link
Copy Markdown
Member Author

Yeah nevermind that's stupid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant