mirror of
https://github.com/better-auth/better-auth.git
synced 2026-08-24 14:34:26 -05:00
fix(two-factor): preserve enforcement on path-less endpoints
Restore the pre-patch semantics for the after-hook matcher: a missing `context.path` must still match so virtual endpoints that mint a session are not silently exempt from 2FA. The `/two-factor/` and session-transition prefix checks now apply only when `path` is set. Also trim the `shouldEnforce` JSDoc; the behavioral detail lives in the Enforcement scope section of the 2FA docs.
This commit is contained in:
@@ -380,14 +380,15 @@ export const twoFactor = <O extends TwoFactorOptions>(options?: O) => {
|
||||
matcher(context) {
|
||||
if (context.context.newSession == null) return false;
|
||||
const path = context.path;
|
||||
if (!path) return false;
|
||||
if (path.startsWith("/two-factor/")) return false;
|
||||
if (
|
||||
SESSION_TRANSITION_PATH_PREFIXES.some((prefix) =>
|
||||
path.startsWith(prefix),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
if (path) {
|
||||
if (path.startsWith("/two-factor/")) return false;
|
||||
if (
|
||||
SESSION_TRANSITION_PATH_PREFIXES.some((prefix) =>
|
||||
path.startsWith(prefix),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -66,29 +66,17 @@ export interface TwoFactorOptions {
|
||||
*/
|
||||
trustDeviceMaxAge?: number | undefined;
|
||||
/**
|
||||
* Decides whether to challenge 2FA on a given sign-in.
|
||||
* Decides whether to challenge 2FA on a given sign-in. Return `true`
|
||||
* to challenge, `false` to skip. Setting this option replaces the
|
||||
* built-in decision, including the passkey UV exemption.
|
||||
*
|
||||
* Return `true` to challenge 2FA for this request; return `false` to
|
||||
* skip. Setting this option replaces the built-in enforcement
|
||||
* decision (including the passkey user-verification exemption).
|
||||
* Same-user session rewrites (session refresh, `updateUser`) and
|
||||
* session-transition endpoints (admin impersonation, multi-session
|
||||
* switching) are never matched and cannot be overridden.
|
||||
*
|
||||
* When omitted, 2FA is challenged on every sign-in that creates a
|
||||
* new session, with one built-in exception: passkey sign-ins whose
|
||||
* assertion confirmed user verification (UV) are not challenged,
|
||||
* since a UV-verified passkey already satisfies MFA.
|
||||
* The authenticating user is available at `ctx.context.newSession.user`.
|
||||
*
|
||||
* Two guards run before this callback and cannot be overridden:
|
||||
* same-user session rewrites (session refresh, `updateUser`) are
|
||||
* never challenged, and session-transition endpoints (admin
|
||||
* impersonation, multi-session switching) are never matched.
|
||||
*
|
||||
* The callback receives the endpoint context. The authenticating
|
||||
* user is available at `ctx.context.newSession.user`.
|
||||
*
|
||||
* Use this option to skip 2FA on flows where the upstream provider
|
||||
* is trusted to enforce it (for example, OAuth callbacks where the
|
||||
* provider already required MFA), or to force a challenge on flows
|
||||
* that the built-in logic would otherwise skip.
|
||||
* @see {@link https://better-auth.com/docs/plugins/2fa#enforcement-scope}
|
||||
*/
|
||||
shouldEnforce?:
|
||||
| ((ctx: GenericEndpointContext) => boolean | Promise<boolean>)
|
||||
|
||||
Reference in New Issue
Block a user