security(relay): minor hardening follow-ups from PR review

- Uniform auth responses remove the session-enumeration oracle: unknown
  session and wrong password now return an identical 401 at the API layer and
  an identical WS handshake/close; desktop-status and the 503 "not connected"
  signal are only exposed after successful auth.
- Session-count cap re-checked after bcrypt.hash so concurrent creates can't
  overshoot maxSessions.
- Relay web client reconnect backoff resets only after a successful auth
  response (an accept-then-close server no longer defeats the backoff).
- idGenerator comment corrected to match the rejection-sampling; drop unused
  recordFailure return value.
- DEPLOY.md: document ALLOWED_ORIGINS for reverse-proxy deployments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 18:13:33 -07:00
parent 74bd3c2e98
commit b5e5bed7dd
7 changed files with 78 additions and 41 deletions
+5 -5
View File
@@ -33,10 +33,12 @@ export function getLockoutRemaining(key: string): number {
}
/**
* Record a failed auth attempt. Returns the lockout remaining (ms) after
* this failure is applied (0 if still not locked).
* Record a failed auth attempt. Locks the key out for
* `authLockoutDurationMs` once `authLockoutMaxAttempts` failures accumulate
* within `authLockoutWindowMs`. Callers query the resulting lockout via
* getLockoutRemaining(), so this returns nothing.
*/
export function recordFailure(key: string): number {
export function recordFailure(key: string): void {
const now = Date.now();
let rec = records.get(key);
@@ -51,8 +53,6 @@ export function recordFailure(key: string): number {
if (rec.count >= config.authLockoutMaxAttempts) {
rec.lockedUntil = now + config.authLockoutDurationMs;
}
return rec.lockedUntil > now ? rec.lockedUntil - now : 0;
}
/**