fix(auth): 邮箱验证码按账号累计错误次数封顶并通知

This commit is contained in:
Lemon-miaow committed 2026-09-24 15:37:01 +08:00
1 parent 18e2397272
commit 15f729ffea
17 files changed
+664 -37

No files matched your search

+36 -11
View File
@@ -2209,7 +2209,9 @@ paths:
purpose. An address with no account returns the SAME 202 with no code minted,
and the per-recipient cooldown is kept on that path too, so probing reveals
nothing (existence is learnt only at the sanctioned /auth/options oracle).
Gated on local_auth_enabled.
An account that spent its daily wrong-code budget (10 per 24h, across every
code) also gets the same 202 and no mail until the window ends. Gated on
local_auth_enabled.
x-felis-face: [external]
x-felis-tier: public
security: []
@@ -2268,7 +2270,10 @@ paths:
under the login purpose, and on success mints a host-only felis_session. An
unknown address, a wrong or expired code, and an attempt-exhausted code all
return the IDENTICAL 400 invalid_code, so the door is not an existence or
lockout oracle. Staff are refused (403) — but only AFTER a valid code is
lockout oracle. The 10th wrong code in 24h locks the door for that account
until the window ends (the right code then also reads as invalid_code); the
owner is told by mail once, and the lock is audited as auth.otp.locked.
Staff are refused (403) — but only AFTER a valid code is
redeemed, so only the account owner can ever reach that refusal.
x-felis-face: [external]
x-felis-tier: public
@@ -2324,7 +2329,8 @@ paths:
staff address, opens an op_login request, and mails a one-time code under the
op_login purpose, returning the request handle the browser polls. A non-staff
or unknown address gets the SAME 202 with a random, non-persisted handle and no
mail, so this never becomes a staff-enumeration oracle. Gated on
mail, so this never becomes a staff-enumeration oracle. A staff account that
spent its daily wrong-code budget gets the same neutral 202. Gated on
local_auth_enabled.
x-felis-face: [external]
x-felis-tier: public
@@ -2415,7 +2421,7 @@ paths:
Public, pre-session final leg: mints a host-only staff session only when BOTH
factors have landed — the request is approved-and-live AND the mailed code
verifies. Every failure (unknown handle, not-yet-approved, wrong or locked code,
lost race) collapses into one uniform 400 op_login_invalid, so a code-less
an account past its daily wrong-code budget, lost race) collapses into one uniform 400 op_login_invalid, so a code-less
caller learns nothing. Admin is re-asserted before the session is issued.
x-felis-face: [external]
x-felis-tier: public
@@ -3768,6 +3774,14 @@ paths:
schema: { $ref: '#/components/schemas/Error' }
'401':
$ref: '#/components/responses/Unauthorized'
'429':
description: >-
Resend requested before the cooldown elapsed (otp_resend_cooldown); or the
account spent its daily wrong-code budget (otp_account_locked, with
Retry-After).
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'502':
$ref: '#/components/responses/MailUndeliverable'
@@ -3779,8 +3793,10 @@ paths:
description: >
Consumes a previously delivered code for the authenticated principal. On
success the user's email is written and email_verified is set true. Too many
incorrect attempts lock the code (429); an unknown, expired, consumed, or
mismatched code is a 400.
incorrect attempts lock the code (429 otp_locked); 10 wrong codes in 24h,
counted across every code, lock the account's email-code door until the
window ends (429 otp_account_locked with Retry-After). An unknown, expired,
consumed, or mismatched code is a 400.
x-felis-face: [external]
x-felis-tier: app
security: [{ accessJWT: [] }]
@@ -3812,7 +3828,9 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
'429':
description: Too many incorrect attempts; the code is locked.
description: >-
Too many incorrect attempts on this code (otp_locked), or the account's
daily wrong-code budget is spent (otp_account_locked, with Retry-After).
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
@@ -4070,7 +4088,10 @@ paths:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'429':
description: Resend requested before the cooldown elapsed.
description: >-
Resend requested before the cooldown elapsed (otp_resend_cooldown), or the
account's daily wrong-code budget is spent (otp_account_locked, with
Retry-After).
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
@@ -4085,8 +4106,9 @@ paths:
description: >
Consumes the fresh migrate-purpose email code for the caller's initiated
migration and advances it to confirmed with confirm_factor email_otp. Too many
wrong attempts lock the code (429 otp_locked); an unknown, expired, consumed, or
mismatched code is a 400 invalid_code.
wrong attempts lock the code (429 otp_locked), and 10 wrong codes in 24h lock
the account's email-code door (429 otp_account_locked with Retry-After); an
unknown, expired, consumed, or mismatched code is a 400 invalid_code.
x-felis-face: [external]
x-felis-tier: app
security: [{ accessJWT: [] }]
@@ -4127,7 +4149,10 @@ paths:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'429':
description: The code is locked after too many wrong attempts (otp_locked).
description: >-
The code is locked after too many wrong attempts (otp_locked), or the
account's daily wrong-code budget is spent (otp_account_locked, with
Retry-After).
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }