@connextar/house 0.13.2 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/step-up-client-index.d.ts +7 -0
- package/dist/auth/step-up-client-index.d.ts.map +1 -0
- package/dist/auth/step-up-client-index.js +7 -0
- package/dist/auth/step-up-client-index.js.map +1 -0
- package/dist/auth/step-up-client.d.ts +31 -0
- package/dist/auth/step-up-client.d.ts.map +1 -0
- package/dist/auth/step-up-client.js +20 -0
- package/dist/auth/step-up-client.js.map +1 -0
- package/dist/auth/step-up-index.d.ts +8 -0
- package/dist/auth/step-up-index.d.ts.map +1 -0
- package/dist/auth/step-up-index.js +8 -0
- package/dist/auth/step-up-index.js.map +1 -0
- package/dist/auth/step-up-routes.d.ts +73 -0
- package/dist/auth/step-up-routes.d.ts.map +1 -0
- package/dist/auth/step-up-routes.js +59 -0
- package/dist/auth/step-up-routes.js.map +1 -0
- package/dist/auth/step-up.d.ts +179 -0
- package/dist/auth/step-up.d.ts.map +1 -0
- package/dist/auth/step-up.js +308 -0
- package/dist/auth/step-up.js.map +1 -0
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -174,6 +174,8 @@ link or a social sign-in now ends up exactly where a password sign-in does:
|
|
|
174
174
|
|
|
175
175
|
Passkeys stay a single step. A passkey proves possession of a device and resists
|
|
176
176
|
phishing, so it is excluded and cannot be added to the list.
|
|
177
|
+
Adding a passkey is another matter: see
|
|
178
|
+
[`auth/step-up`](#connextarhouseauthstep-up-authstep-upclient).
|
|
177
179
|
|
|
178
180
|
**How it works.** This module copies none of Better Auth's pending-state
|
|
179
181
|
internals: no cookie names, no signing, no identifiers. On these routes it runs
|
|
@@ -321,6 +323,231 @@ PharmaLine (1.6.33). All of them already use `/auth/two-factor` and `?next=`.
|
|
|
321
323
|
proof.
|
|
322
324
|
8. Optional: the confirm page for link scanners.
|
|
323
325
|
|
|
326
|
+
### `@connextar/house/auth/step-up`, `/auth/step-up/client`
|
|
327
|
+
|
|
328
|
+
A new way in needs a recent proof of an existing one.
|
|
329
|
+
|
|
330
|
+
`@better-auth/passkey` lets any session under a day old add a passkey.
|
|
331
|
+
`/passkey/generate-register-options` and `/passkey/verify-registration` check
|
|
332
|
+
only `freshSessionMiddleware` (`session.freshAge`, one day by default): no
|
|
333
|
+
password, no code. A passkey then signs in in one step, because it is left out
|
|
334
|
+
of the second-factor challenge on purpose. So a stolen or borrowed session
|
|
335
|
+
becomes a lasting way in that skips two-factor, and a password reset does not
|
|
336
|
+
remove it, because a reset revokes sessions, not passkeys. Think of a shared
|
|
337
|
+
computer left signed in, a cookie taken through XSS, or a trusted-device sign-in
|
|
338
|
+
that skipped the code. All seven house apps with passkeys had this hole. Their
|
|
339
|
+
own "second factor proven" plugins closed it for platform administrators only.
|
|
340
|
+
|
|
341
|
+
Two-factor management is not part of this hole. Better Auth's
|
|
342
|
+
`/two-factor/enable`, `/two-factor/disable` and `/two-factor/generate-backup-codes`
|
|
343
|
+
already ask for the password.
|
|
344
|
+
|
|
345
|
+
`stepUp()` refuses both passkey registration routes until the session holds a
|
|
346
|
+
confirmation younger than the window (ten minutes by default). What counts as a
|
|
347
|
+
confirmation depends on the account:
|
|
348
|
+
|
|
349
|
+
| The account has | `needs` | What confirms it |
|
|
350
|
+
| ----------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
351
|
+
| two-factor on | `code` | The authenticator code or a backup code, entered now. The client calls Better Auth's own `/two-factor/verify-totp` or `/two-factor/verify-backup-code` while signed in. |
|
|
352
|
+
| no two-factor, a password | `password` | The password, sent to `POST /step-up/password`. |
|
|
353
|
+
| neither (email link or social only) | `recent-sign-in` | A sign-in within the window: the session itself is younger than `windowSeconds`. There is nothing to type, so the screen asks them to sign in again. |
|
|
354
|
+
|
|
355
|
+
- **A confirmation belongs to one session.** It is a row in Better Auth's
|
|
356
|
+
`verification` table (`step-up:<session id>`, with the user id as its value,
|
|
357
|
+
expiring after the window). A confirmation made by another session, or
|
|
358
|
+
recorded for another user, never counts. Using it does not spend it: the
|
|
359
|
+
options call and the verify call both need it, and a second passkey added
|
|
360
|
+
inside the window needs nothing more.
|
|
361
|
+
- **The session is read from the session store,** never from the cookie cache.
|
|
362
|
+
A revoked session gets a 401 even while its cached cookie is still valid.
|
|
363
|
+
- **The refusal** is `403` with
|
|
364
|
+
`{ code: "STEP_UP_REQUIRED", message: "Confirm it's you before adding a passkey.", needs }`,
|
|
365
|
+
so the client knows which prompt to show.
|
|
366
|
+
- **Wrong answers.** Better Auth's two-factor lockout and attempt limit apply
|
|
367
|
+
only to a sign-in. A code checked with a live session is held back by the rate
|
|
368
|
+
limiter and nothing else. So each session gets `maxAttempts` wrong answers
|
|
369
|
+
(five by default, codes and passwords together). After that it has to sign in
|
|
370
|
+
again, and its live code checks are refused too (`403 STEP_UP_LOCKED`). A
|
|
371
|
+
stolen session cannot keep guessing.
|
|
372
|
+
- **Deleting a passkey is not gated.** Removing a way in does not add one.
|
|
373
|
+
- **The apps' administrator rule is unchanged** and still applies on top of
|
|
374
|
+
this. An administrator's passkey needs both a second-factor-proven session
|
|
375
|
+
and a step-up confirmation. Either refusal can stop it, and neither breaks the
|
|
376
|
+
other.
|
|
377
|
+
|
|
378
|
+
**How the code path works.** None of the two-factor plugin is copied: no secret,
|
|
379
|
+
no decryption. Called with a live session, Better Auth's verify routes check the
|
|
380
|
+
code and create nothing. They answer with the token of the session they were
|
|
381
|
+
called with. When such a call succeeds, this plugin records a confirmation for
|
|
382
|
+
that session. It does this only if the account already had two-factor on
|
|
383
|
+
_before_ the call. On an account still enrolling, the same call ends enrolment
|
|
384
|
+
and replaces the session, and it proves only that the person holds the
|
|
385
|
+
authenticator they just added. A code that finishes a sign-in does not count either: it
|
|
386
|
+
created the session rather than confirming one, so the person confirms once
|
|
387
|
+
more before adding a passkey. `step-up.test.ts` pins each of these
|
|
388
|
+
facts, and `init` refuses to start if the two-factor plugin is installed without
|
|
389
|
+
its verify routes, or the passkey plugin without its registration routes.
|
|
390
|
+
|
|
391
|
+
On an account with two-factor on, someone who has the session _and_ the password
|
|
392
|
+
can re-key the authenticator (Better Auth's `/two-factor/enable`) and then
|
|
393
|
+
produce codes. So for those accounts the real bar is "the code, or the
|
|
394
|
+
password". It is never the session alone. That only stops holding under
|
|
395
|
+
`twoFactor({ allowPasswordless: true })`, which no house app sets, and `init`
|
|
396
|
+
warns if one does.
|
|
397
|
+
|
|
398
|
+
The tests run a real Better Auth instance: memory adapter, the real `twoFactor`,
|
|
399
|
+
`passkey` and `magicLink` plugins, a software authenticator, and requests through
|
|
400
|
+
`auth.handler` with a cookie jar. They run against **1.7.2** with
|
|
401
|
+
`@better-auth/passkey` 1.7.2 and against **1.6.33** with
|
|
402
|
+
`@better-auth/passkey` 1.6.33 (`better-auth-passkey-1-6`, re-pointed by the
|
|
403
|
+
`better-auth-1.6` Vitest project, the same way as second-factor).
|
|
404
|
+
|
|
405
|
+
#### Server
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
import { stepUp } from "@connextar/house/auth/step-up";
|
|
409
|
+
|
|
410
|
+
plugins: [
|
|
411
|
+
twoFactor({ … }),
|
|
412
|
+
passkey({ … }),
|
|
413
|
+
secondFactorSessions(), // the app's own admin proof: unchanged
|
|
414
|
+
secondFactorEverywhere(),
|
|
415
|
+
stepUp({ // after twoFactor() and passkey(), before nextCookies()
|
|
416
|
+
onPasskeyAdded: ({ user, passkey }) => sendPasskeyAddedEmail(user.email, passkey.name),
|
|
417
|
+
}),
|
|
418
|
+
magicLink({ … }),
|
|
419
|
+
],
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
| Option | Default | What it does |
|
|
423
|
+
| ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
424
|
+
| `windowSeconds` | `600` | How long a confirmation lasts, and how young a session must be to count as a recent sign-in. |
|
|
425
|
+
| `routes` | `STEP_UP_PASSKEY_ROUTES` | The routes that need a confirmation. To add one, keep the defaults: `[...STEP_UP_PASSKEY_ROUTES, "/my/route"]`. Other routes get a plain message. Router endpoints only: a server-only method such as `setPassword` has no path, so listing it guards nothing (see below). |
|
|
426
|
+
| `maxAttempts` | `5` | Wrong codes and passwords one session may give before it has to sign in again. |
|
|
427
|
+
| `onPasskeyAdded` | none | Called after `/passkey/verify-registration` succeeds. See below. |
|
|
428
|
+
| `enabled` | `true` | `false` turns the refusal off and keeps the endpoints, so a client that asks for status still gets an answer. `onPasskeyAdded` still runs. |
|
|
429
|
+
|
|
430
|
+
`POST /step-up/password` is rate-limited the same way as Better Auth's own
|
|
431
|
+
two-factor routes: three tries in ten seconds, wherever the app's rate limiter
|
|
432
|
+
is on. A wrong password gets Better Auth's own `400 INVALID_PASSWORD`. An account
|
|
433
|
+
that does not confirm with a password gets `400 STEP_UP_PASSWORD_NOT_ACCEPTED`
|
|
434
|
+
with its `needs`.
|
|
435
|
+
|
|
436
|
+
A passkey-first sign-up (`passkey({ registration: { requireSession: false } })`)
|
|
437
|
+
is let through when there is no session, because there is no existing way in to
|
|
438
|
+
prove. With a session, the rule applies as usual.
|
|
439
|
+
|
|
440
|
+
#### The client
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
import { stepUpClient } from "@connextar/house/auth/step-up/client";
|
|
444
|
+
|
|
445
|
+
export const authClient = createAuthClient({ plugins: [twoFactorClient(), passkeyClient(), stepUpClient()] });
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
`authClient.stepUp.status()` answers `{ needs, confirmed, confirmedUntil, locked, sessionFresh }`,
|
|
449
|
+
and `authClient.stepUp.password({ password })` gives the same shape on success.
|
|
450
|
+
Better Auth's client parser turns `confirmedUntil` into a `Date`. The
|
|
451
|
+
client entry imports nothing from Better Auth at runtime. The helpers are also
|
|
452
|
+
on `@connextar/house/auth`.
|
|
453
|
+
|
|
454
|
+
The flow for "Add a passkey":
|
|
455
|
+
|
|
456
|
+
1. `status()`. If `sessionFresh` is false, show the sign-in-again message and
|
|
457
|
+
stop: the passkey plugin refuses a session older than Better Auth's
|
|
458
|
+
`session.freshAge` (a day by default) whatever it confirms, and with
|
|
459
|
+
month-long sessions that is most people. Asking for a password first and
|
|
460
|
+
then saying "sign in again" is the thing to avoid. If `confirmed`, go to
|
|
461
|
+
step 3. (Setting a first password has no such limit; that screen can ignore
|
|
462
|
+
`sessionFresh`.)
|
|
463
|
+
2. Show the prompt that `needs` asks for, in the app's own dialog with its own
|
|
464
|
+
`PasswordInput`:
|
|
465
|
+
- `code`: a code field, then `authClient.twoFactor.verifyTotp({ code })`, or
|
|
466
|
+
`verifyBackupCode({ code })` behind "Use a backup code".
|
|
467
|
+
- `password`: a password field, then `authClient.stepUp.password({ password })`.
|
|
468
|
+
- `recent-sign-in`: "For your security, sign in again to add a passkey", with
|
|
469
|
+
a button that signs out and returns to the sign-in page with `?next=` back
|
|
470
|
+
here.
|
|
471
|
+
- `locked`: the same sign-in-again message.
|
|
472
|
+
3. `authClient.passkey.addPasskey({ name })`.
|
|
473
|
+
4. If that answers `STEP_UP_REQUIRED` (the window ran out while the person was
|
|
474
|
+
at the browser's passkey sheet), prompt and retry once:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
import { isStepUpRequired, stepUpNeedsOf } from "@connextar/house/auth/step-up/client";
|
|
478
|
+
|
|
479
|
+
let result = await authClient.passkey.addPasskey({ name });
|
|
480
|
+
if (isStepUpRequired(result.error) && (await confirmItsYou(stepUpNeedsOf(result.error)))) {
|
|
481
|
+
result = await authClient.passkey.addPasskey({ name });
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
There is no React component here. Each app already has its own dialog, its own
|
|
486
|
+
`PasswordInput` and its own code field.
|
|
487
|
+
|
|
488
|
+
#### The email
|
|
489
|
+
|
|
490
|
+
`onPasskeyAdded({ user, passkey: { id, name?, createdAt? }, request? })` runs once
|
|
491
|
+
for each passkey added, after the passkey is stored. Use it to send "A passkey
|
|
492
|
+
was added to your account" through the app's own mailer. The email should say
|
|
493
|
+
when, give the name if there is one, and include "If this wasn't you, remove it
|
|
494
|
+
in Settings → Security and change your password". The callback cannot fail or
|
|
495
|
+
undo the add: an error is logged and the passkey stays. If Better Auth's
|
|
496
|
+
`advanced.backgroundTasks` is set (for example `waitUntil` on Vercel), the
|
|
497
|
+
callback runs in the background. Otherwise the response waits for it. `request`
|
|
498
|
+
is there for the app's own audit line (IP address, user agent).
|
|
499
|
+
|
|
500
|
+
#### Setting a first password is the same hole
|
|
501
|
+
|
|
502
|
+
An account that signs in by email link or Google has no password, and the house
|
|
503
|
+
apps let it add one from Settings through their own `POST /api/auth/set-password`,
|
|
504
|
+
which calls Better Auth's `setPassword`. A session alone was enough, so a
|
|
505
|
+
borrowed session could add a password: a new way in, exactly like a passkey.
|
|
506
|
+
|
|
507
|
+
`routes` cannot guard it. `setPassword` is server-only and has no path (checked
|
|
508
|
+
on 1.7.2 by senco's adoption, which pins `auth.api.setPassword.path` as
|
|
509
|
+
undefined), so a hook never sees it. Check in the route instead:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
const status = await auth.api.stepUpStatus({ headers: request.headers });
|
|
513
|
+
if (!status.confirmed) {
|
|
514
|
+
return Response.json(
|
|
515
|
+
{ code: "STEP_UP_REQUIRED", message: "Confirm it's you before adding a password.", needs: status.needs },
|
|
516
|
+
{ status: 403 },
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
await auth.api.setPassword({ body: { newPassword }, headers: request.headers });
|
|
520
|
+
// then email "A password was added to your account", never failing the request
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
The answer has the same shape as the plugin's refusal, so `isStepUpRequired`
|
|
524
|
+
recognises it and the same dialog serves both. Such an account has no password
|
|
525
|
+
to type, so `needs` is `recent-sign-in`, or `code` if it has two-factor.
|
|
526
|
+
|
|
527
|
+
#### Adopting it in an app
|
|
528
|
+
|
|
529
|
+
This applies to AltEd, Quickie, PM Tool, senco, KRP, FB Web (1.7.2) and
|
|
530
|
+
PharmaLine (1.6.33).
|
|
531
|
+
|
|
532
|
+
1. Bump `@connextar/house` to 0.14.0.
|
|
533
|
+
2. In `auth.ts`, add `stepUp({ onPasskeyAdded })` after `twoFactor()` and
|
|
534
|
+
`passkey()`, and before `nextCookies()`.
|
|
535
|
+
3. Write `sendPasskeyAddedEmail` next to the app's other auth emails, and pass
|
|
536
|
+
it in `onPasskeyAdded`.
|
|
537
|
+
4. Add `stepUpClient()` to the auth client.
|
|
538
|
+
5. In the "Add a passkey" flow, call `status()` first, show the prompt that
|
|
539
|
+
`needs` asks for, then `addPasskey`. Retry once on `isStepUpRequired`. The
|
|
540
|
+
code prompt calls `twoFactor.verifyTotp` or `verifyBackupCode` while signed in.
|
|
541
|
+
Do not route it through the sign-in code screen.
|
|
542
|
+
6. Leave the admin second-factor proof rule (`second-factor-session.ts`, AltEd
|
|
543
|
+
`admin-second-factor.ts`) as it is. Update only its comment that says
|
|
544
|
+
"Better Auth lets any fresh session register a passkey": that is now true
|
|
545
|
+
only for administrators who have confirmed.
|
|
546
|
+
7. Add an app test: a signed-in session cannot get passkey registration options
|
|
547
|
+
until it confirms, and then it can.
|
|
548
|
+
8. Copy: the security settings should say that adding a passkey asks for the
|
|
549
|
+
code, the password, or a fresh sign-in, and that an email follows.
|
|
550
|
+
|
|
324
551
|
### `@connextar/house/account`
|
|
325
552
|
|
|
326
553
|
The contract for "your data" and "delete my account" — types, the export notice,
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { TWO_FACTOR_BLOCKED_MESSAGE, TWO_FACTOR_BLOCKED_PATHS, isTwoFactorBlockedPath, magicLinkRefusalRetired, shouldBlockForTwoFactor, totpSecretFromUri, type AuthContextLike, type TwoFactorBlockedPath, type TwoFactorLookup, } from "./two-factor-policy.js";
|
|
2
2
|
export { DEFAULT_CODE_SCREEN_PATH, DEFAULT_NEXT_PARAM, SECOND_FACTOR_CHALLENGE_ROUTES, SECOND_FACTOR_EVERYWHERE_ID, type SecondFactorChallengeRoute, } from "./second-factor-routes.js";
|
|
3
3
|
export { magicLinkConfirmHref, magicLinkVerifyHref, safeNextPath, secondFactorNext } from "./second-factor-next.js";
|
|
4
|
+
export { DEFAULT_STEP_UP_WINDOW_SECONDS, STEP_UP_ERROR_CODES, STEP_UP_ID, STEP_UP_PASSKEY_ROUTES, isStepUpRequired, stepUpNeedsOf, type StepUpErrorCode, type StepUpNeeds, type StepUpStatus, } from "./step-up-routes.js";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,8BAA8B,EAC9B,2BAA2B,EAC3B,KAAK,0BAA0B,GAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,8BAA8B,EAC9B,2BAA2B,EAC3B,KAAK,0BAA0B,GAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACpH,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,UAAU,EACV,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC"}
|
package/dist/auth/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { TWO_FACTOR_BLOCKED_MESSAGE, TWO_FACTOR_BLOCKED_PATHS, isTwoFactorBlockedPath, magicLinkRefusalRetired, shouldBlockForTwoFactor, totpSecretFromUri, } from "./two-factor-policy.js";
|
|
2
2
|
export { DEFAULT_CODE_SCREEN_PATH, DEFAULT_NEXT_PARAM, SECOND_FACTOR_CHALLENGE_ROUTES, SECOND_FACTOR_EVERYWHERE_ID, } from "./second-factor-routes.js";
|
|
3
3
|
export { magicLinkConfirmHref, magicLinkVerifyHref, safeNextPath, secondFactorNext } from "./second-factor-next.js";
|
|
4
|
+
export { DEFAULT_STEP_UP_WINDOW_SECONDS, STEP_UP_ERROR_CODES, STEP_UP_ID, STEP_UP_PASSKEY_ROUTES, isStepUpRequired, stepUpNeedsOf, } from "./step-up-routes.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAIlB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,8BAA8B,EAC9B,2BAA2B,GAE5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAIlB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,8BAA8B,EAC9B,2BAA2B,GAE5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACpH,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,UAAU,EACV,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GAId,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/auth/step-up/client` — the client half: a Better Auth client plugin, and the helpers a dialog
|
|
3
|
+
* needs to recognise the refusal. Nothing here imports Better Auth at runtime.
|
|
4
|
+
*/
|
|
5
|
+
export { stepUpClient } from "./step-up-client.js";
|
|
6
|
+
export { STEP_UP_ERROR_CODES, isStepUpRequired, stepUpNeedsOf, type StepUpErrorCode, type StepUpNeeds, type StepUpStatus, } from "./step-up-routes.js";
|
|
7
|
+
//# sourceMappingURL=step-up-client-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-client-index.d.ts","sourceRoot":"","sources":["../../src/auth/step-up-client-index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/auth/step-up/client` — the client half: a Better Auth client plugin, and the helpers a dialog
|
|
3
|
+
* needs to recognise the refusal. Nothing here imports Better Auth at runtime.
|
|
4
|
+
*/
|
|
5
|
+
export { stepUpClient } from "./step-up-client.js";
|
|
6
|
+
export { STEP_UP_ERROR_CODES, isStepUpRequired, stepUpNeedsOf, } from "./step-up-routes.js";
|
|
7
|
+
//# sourceMappingURL=step-up-client-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-client-index.js","sourceRoot":"","sources":["../../src/auth/step-up-client-index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,GAId,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { stepUp } from "./step-up.js";
|
|
2
|
+
/**
|
|
3
|
+
* The client half of `stepUp()`: `authClient.stepUp.status()` and `authClient.stepUp.password({ password })`, typed
|
|
4
|
+
* from the server plugin. A code confirms through Better Auth's own `authClient.twoFactor.verifyTotp({ code })` (or
|
|
5
|
+
* `verifyBackupCode`), called while signed in; nothing here wraps it.
|
|
6
|
+
*
|
|
7
|
+
* Imports nothing from Better Auth at runtime, so it costs a client bundle only its own few lines.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stepUpClient(): {
|
|
10
|
+
id: "step-up";
|
|
11
|
+
$InferServerPlugin: ReturnType<typeof stepUp>;
|
|
12
|
+
pathMethods: {
|
|
13
|
+
"/step-up/status": "GET";
|
|
14
|
+
"/step-up/password": "POST";
|
|
15
|
+
};
|
|
16
|
+
$ERROR_CODES: {
|
|
17
|
+
readonly STEP_UP_REQUIRED: {
|
|
18
|
+
readonly code: "STEP_UP_REQUIRED";
|
|
19
|
+
readonly message: "Confirm it's you before adding a passkey.";
|
|
20
|
+
};
|
|
21
|
+
readonly STEP_UP_LOCKED: {
|
|
22
|
+
readonly code: "STEP_UP_LOCKED";
|
|
23
|
+
readonly message: "Too many wrong attempts. Sign in again, then confirm it's you.";
|
|
24
|
+
};
|
|
25
|
+
readonly STEP_UP_PASSWORD_NOT_ACCEPTED: {
|
|
26
|
+
readonly code: "STEP_UP_PASSWORD_NOT_ACCEPTED";
|
|
27
|
+
readonly message: "This account doesn't confirm with a password.";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=step-up-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-client.d.ts","sourceRoot":"","sources":["../../src/auth/step-up-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C;;;;;;GAMG;AACH,wBAAgB,YAAY;;wBAGE,UAAU,CAAC,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;EAOtD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { STEP_UP_ERROR_CODES, STEP_UP_ID } from "./step-up-routes.js";
|
|
2
|
+
/**
|
|
3
|
+
* The client half of `stepUp()`: `authClient.stepUp.status()` and `authClient.stepUp.password({ password })`, typed
|
|
4
|
+
* from the server plugin. A code confirms through Better Auth's own `authClient.twoFactor.verifyTotp({ code })` (or
|
|
5
|
+
* `verifyBackupCode`), called while signed in; nothing here wraps it.
|
|
6
|
+
*
|
|
7
|
+
* Imports nothing from Better Auth at runtime, so it costs a client bundle only its own few lines.
|
|
8
|
+
*/
|
|
9
|
+
export function stepUpClient() {
|
|
10
|
+
return {
|
|
11
|
+
id: STEP_UP_ID,
|
|
12
|
+
$InferServerPlugin: {},
|
|
13
|
+
pathMethods: {
|
|
14
|
+
"/step-up/status": "GET",
|
|
15
|
+
"/step-up/password": "POST",
|
|
16
|
+
},
|
|
17
|
+
$ERROR_CODES: STEP_UP_ERROR_CODES,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=step-up-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-client.js","sourceRoot":"","sources":["../../src/auth/step-up-client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,EAAE,EAAE,UAAU;QACd,kBAAkB,EAAE,EAA+B;QACnD,WAAW,EAAE;YACX,iBAAiB,EAAE,KAAK;YACxB,mBAAmB,EAAE,MAAM;SAC5B;QACD,YAAY,EAAE,mBAAmB;KACD,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/auth/step-up` — the server half: a Better Auth plugin. Kept off `@connextar/house/auth` so that
|
|
3
|
+
* entry point stays free of any Better Auth import and safe for client components; the client plugin is
|
|
4
|
+
* `@connextar/house/auth/step-up/client`.
|
|
5
|
+
*/
|
|
6
|
+
export { stepUp, type PasskeyAddedData, type StepUpOptions } from "./step-up.js";
|
|
7
|
+
export { DEFAULT_STEP_UP_MAX_ATTEMPTS, DEFAULT_STEP_UP_WINDOW_SECONDS, STEP_UP_CODE_ROUTES, STEP_UP_ERROR_CODES, STEP_UP_ID, STEP_UP_PASSKEY_ROUTES, isStepUpRequired, stepUpNeedsOf, type StepUpErrorCode, type StepUpNeeds, type StepUpStatus, } from "./step-up-routes.js";
|
|
8
|
+
//# sourceMappingURL=step-up-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-index.d.ts","sourceRoot":"","sources":["../../src/auth/step-up-index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,EAC9B,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/auth/step-up` — the server half: a Better Auth plugin. Kept off `@connextar/house/auth` so that
|
|
3
|
+
* entry point stays free of any Better Auth import and safe for client components; the client plugin is
|
|
4
|
+
* `@connextar/house/auth/step-up/client`.
|
|
5
|
+
*/
|
|
6
|
+
export { stepUp } from "./step-up.js";
|
|
7
|
+
export { DEFAULT_STEP_UP_MAX_ATTEMPTS, DEFAULT_STEP_UP_WINDOW_SECONDS, STEP_UP_CODE_ROUTES, STEP_UP_ERROR_CODES, STEP_UP_ID, STEP_UP_PASSKEY_ROUTES, isStepUpRequired, stepUpNeedsOf, } from "./step-up-routes.js";
|
|
8
|
+
//# sourceMappingURL=step-up-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-index.js","sourceRoot":"","sources":["../../src/auth/step-up-index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAA6C,MAAM,cAAc,CAAC;AACjF,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,EAC9B,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GAId,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary shared by the step-up server plugin (`@connextar/house/auth/step-up`), its client plugin
|
|
3
|
+
* (`@connextar/house/auth/step-up/client`) and the pure helpers in `@connextar/house/auth`. Kept free of any Better
|
|
4
|
+
* Auth import so a client component can recognise a refusal without pulling the server in.
|
|
5
|
+
*/
|
|
6
|
+
/** The Better Auth plugin id of `stepUp()` and `stepUpClient()`. */
|
|
7
|
+
export declare const STEP_UP_ID = "step-up";
|
|
8
|
+
/**
|
|
9
|
+
* The routes that add a passkey — a new way in. `@better-auth/passkey` guards both with nothing but a session under
|
|
10
|
+
* a day old (`session.freshAge`); `stepUp()` puts a recent proof of an existing way in in front of them.
|
|
11
|
+
*/
|
|
12
|
+
export declare const STEP_UP_PASSKEY_ROUTES: readonly ["/passkey/generate-register-options", "/passkey/verify-registration"];
|
|
13
|
+
/**
|
|
14
|
+
* Better Auth's own code checks. Called with a live session on an account that already has two-factor on, they check
|
|
15
|
+
* the code and create nothing — which is what `stepUp()` records as a confirmation.
|
|
16
|
+
*/
|
|
17
|
+
export declare const STEP_UP_CODE_ROUTES: readonly ["/two-factor/verify-totp", "/two-factor/verify-backup-code"];
|
|
18
|
+
/** How long a confirmation lasts unless an app says otherwise: ten minutes. */
|
|
19
|
+
export declare const DEFAULT_STEP_UP_WINDOW_SECONDS = 600;
|
|
20
|
+
/** How many wrong answers one session may give before it has to sign in again. */
|
|
21
|
+
export declare const DEFAULT_STEP_UP_MAX_ATTEMPTS = 5;
|
|
22
|
+
/**
|
|
23
|
+
* What confirms "it's you" for this account:
|
|
24
|
+
*
|
|
25
|
+
* - `code` — two-factor is on: the authenticator code or a backup code, entered now.
|
|
26
|
+
* - `password` — no two-factor, but a password: the password.
|
|
27
|
+
* - `recent-sign-in` — neither (email link or social sign-in only): a sign-in within the window. Nothing to type;
|
|
28
|
+
* the screen asks them to sign in again.
|
|
29
|
+
*/
|
|
30
|
+
export type StepUpNeeds = "code" | "password" | "recent-sign-in";
|
|
31
|
+
/** `GET /step-up/status`, and what `POST /step-up/password` answers on success. */
|
|
32
|
+
export interface StepUpStatus {
|
|
33
|
+
needs: StepUpNeeds;
|
|
34
|
+
/** Whether this session may add a passkey now. */
|
|
35
|
+
confirmed: boolean;
|
|
36
|
+
/** When the confirmation lapses (ISO 8601), or null when there is none. */
|
|
37
|
+
confirmedUntil: string | null;
|
|
38
|
+
/** Too many wrong answers on this session: it has to sign in again before it can confirm. */
|
|
39
|
+
locked: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the session is young enough for Better Auth to let it add a passkey at all (`session.freshAge`, a day
|
|
42
|
+
* by default; always true when that check is off). When false, confirming changes nothing: the passkey plugin
|
|
43
|
+
* answers SESSION_NOT_FRESH whatever this session proves. Ask them to sign in again before asking for anything.
|
|
44
|
+
* Setting a first password has no such limit, so a set-password screen can ignore it.
|
|
45
|
+
*/
|
|
46
|
+
sessionFresh: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** The error codes the plugin answers with. Shaped as Better Auth's own `$ERROR_CODES`. */
|
|
49
|
+
export declare const STEP_UP_ERROR_CODES: {
|
|
50
|
+
readonly STEP_UP_REQUIRED: {
|
|
51
|
+
readonly code: "STEP_UP_REQUIRED";
|
|
52
|
+
readonly message: "Confirm it's you before adding a passkey.";
|
|
53
|
+
};
|
|
54
|
+
readonly STEP_UP_LOCKED: {
|
|
55
|
+
readonly code: "STEP_UP_LOCKED";
|
|
56
|
+
readonly message: "Too many wrong attempts. Sign in again, then confirm it's you.";
|
|
57
|
+
};
|
|
58
|
+
readonly STEP_UP_PASSWORD_NOT_ACCEPTED: {
|
|
59
|
+
readonly code: "STEP_UP_PASSWORD_NOT_ACCEPTED";
|
|
60
|
+
readonly message: "This account doesn't confirm with a password.";
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export type StepUpErrorCode = keyof typeof STEP_UP_ERROR_CODES;
|
|
64
|
+
/** The refusal's message on a route that is not a passkey route (one an app added to `routes`). */
|
|
65
|
+
export declare const STEP_UP_REQUIRED_GENERIC_MESSAGE = "Confirm it's you before making this change.";
|
|
66
|
+
/**
|
|
67
|
+
* True when `error` is the step-up refusal: `{ code: "STEP_UP_REQUIRED" }` as Better Auth's client returns it in
|
|
68
|
+
* `{ error }`, as a thrown `APIError` carries it in `.body`, or wrapped once more in `.error`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isStepUpRequired(error: unknown): boolean;
|
|
71
|
+
/** What the refusal (or a `STEP_UP_PASSWORD_NOT_ACCEPTED`) says confirms this account, or null if it says nothing. */
|
|
72
|
+
export declare function stepUpNeedsOf(error: unknown): StepUpNeeds | null;
|
|
73
|
+
//# sourceMappingURL=step-up-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-routes.d.ts","sourceRoot":"","sources":["../../src/auth/step-up-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oEAAoE;AACpE,eAAO,MAAM,UAAU,YAAY,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,sBAAsB,iFAAkF,CAAC;AAEtH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wEAAyE,CAAC;AAE1G,+EAA+E;AAC/E,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAEjE,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,kDAAkD;IAClD,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6FAA6F;IAC7F,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,2FAA2F;AAC3F,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;CAUtB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAE/D,mGAAmG;AACnG,eAAO,MAAM,gCAAgC,gDAAgD,CAAC;AAa9F;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAExD;AAED,sHAAsH;AACtH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAKhE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary shared by the step-up server plugin (`@connextar/house/auth/step-up`), its client plugin
|
|
3
|
+
* (`@connextar/house/auth/step-up/client`) and the pure helpers in `@connextar/house/auth`. Kept free of any Better
|
|
4
|
+
* Auth import so a client component can recognise a refusal without pulling the server in.
|
|
5
|
+
*/
|
|
6
|
+
/** The Better Auth plugin id of `stepUp()` and `stepUpClient()`. */
|
|
7
|
+
export const STEP_UP_ID = "step-up";
|
|
8
|
+
/**
|
|
9
|
+
* The routes that add a passkey — a new way in. `@better-auth/passkey` guards both with nothing but a session under
|
|
10
|
+
* a day old (`session.freshAge`); `stepUp()` puts a recent proof of an existing way in in front of them.
|
|
11
|
+
*/
|
|
12
|
+
export const STEP_UP_PASSKEY_ROUTES = ["/passkey/generate-register-options", "/passkey/verify-registration"];
|
|
13
|
+
/**
|
|
14
|
+
* Better Auth's own code checks. Called with a live session on an account that already has two-factor on, they check
|
|
15
|
+
* the code and create nothing — which is what `stepUp()` records as a confirmation.
|
|
16
|
+
*/
|
|
17
|
+
export const STEP_UP_CODE_ROUTES = ["/two-factor/verify-totp", "/two-factor/verify-backup-code"];
|
|
18
|
+
/** How long a confirmation lasts unless an app says otherwise: ten minutes. */
|
|
19
|
+
export const DEFAULT_STEP_UP_WINDOW_SECONDS = 600;
|
|
20
|
+
/** How many wrong answers one session may give before it has to sign in again. */
|
|
21
|
+
export const DEFAULT_STEP_UP_MAX_ATTEMPTS = 5;
|
|
22
|
+
/** The error codes the plugin answers with. Shaped as Better Auth's own `$ERROR_CODES`. */
|
|
23
|
+
export const STEP_UP_ERROR_CODES = {
|
|
24
|
+
STEP_UP_REQUIRED: { code: "STEP_UP_REQUIRED", message: "Confirm it's you before adding a passkey." },
|
|
25
|
+
STEP_UP_LOCKED: {
|
|
26
|
+
code: "STEP_UP_LOCKED",
|
|
27
|
+
message: "Too many wrong attempts. Sign in again, then confirm it's you.",
|
|
28
|
+
},
|
|
29
|
+
STEP_UP_PASSWORD_NOT_ACCEPTED: {
|
|
30
|
+
code: "STEP_UP_PASSWORD_NOT_ACCEPTED",
|
|
31
|
+
message: "This account doesn't confirm with a password.",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
/** The refusal's message on a route that is not a passkey route (one an app added to `routes`). */
|
|
35
|
+
export const STEP_UP_REQUIRED_GENERIC_MESSAGE = "Confirm it's you before making this change.";
|
|
36
|
+
const NEEDS = ["code", "password", "recent-sign-in"];
|
|
37
|
+
/** The error body, wherever the caller's library put it: the object itself, `.error` (Better Auth's client) or `.body`. */
|
|
38
|
+
function errorBodies(error) {
|
|
39
|
+
if (!error || typeof error !== "object")
|
|
40
|
+
return [];
|
|
41
|
+
const outer = error;
|
|
42
|
+
return [outer, outer.error, outer.body].filter((body) => !!body && typeof body === "object");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* True when `error` is the step-up refusal: `{ code: "STEP_UP_REQUIRED" }` as Better Auth's client returns it in
|
|
46
|
+
* `{ error }`, as a thrown `APIError` carries it in `.body`, or wrapped once more in `.error`.
|
|
47
|
+
*/
|
|
48
|
+
export function isStepUpRequired(error) {
|
|
49
|
+
return errorBodies(error).some((body) => body.code === STEP_UP_ERROR_CODES.STEP_UP_REQUIRED.code);
|
|
50
|
+
}
|
|
51
|
+
/** What the refusal (or a `STEP_UP_PASSWORD_NOT_ACCEPTED`) says confirms this account, or null if it says nothing. */
|
|
52
|
+
export function stepUpNeedsOf(error) {
|
|
53
|
+
for (const body of errorBodies(error)) {
|
|
54
|
+
if (typeof body.needs === "string" && NEEDS.includes(body.needs))
|
|
55
|
+
return body.needs;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=step-up-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-routes.js","sourceRoot":"","sources":["../../src/auth/step-up-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,oCAAoC,EAAE,8BAA8B,CAAU,CAAC;AAEtH;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,yBAAyB,EAAE,gCAAgC,CAAU,CAAC;AAE1G,+EAA+E;AAC/E,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC;AAElD,kFAAkF;AAClF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AA8B9C,2FAA2F;AAC3F,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,2CAA2C,EAAE;IACpG,cAAc,EAAE;QACd,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,gEAAgE;KAC1E;IACD,6BAA6B,EAAE;QAC7B,IAAI,EAAE,+BAA+B;QACrC,OAAO,EAAE,+CAA+C;KACzD;CACO,CAAC;AAIX,mGAAmG;AACnG,MAAM,CAAC,MAAM,gCAAgC,GAAG,6CAA6C,CAAC;AAE9F,MAAM,KAAK,GAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,gBAAgB,CAAyB,CAAC;AAEhG,2HAA2H;AAC3H,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACnD,MAAM,KAAK,GAAG,KAAgC,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAC5C,CAAC,IAAI,EAAmC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAC9E,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,mBAAmB,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;AACpG,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,KAAoB,CAAC;IACrG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { User } from "better-auth";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { type StepUpNeeds, type StepUpStatus } from "./step-up-routes.js";
|
|
4
|
+
/**
|
|
5
|
+
* A new way in needs a recent proof of an existing one.
|
|
6
|
+
*
|
|
7
|
+
* `@better-auth/passkey` lets any session under a day old (`session.freshAge`) register a passkey — no password, no
|
|
8
|
+
* code. A passkey then signs in in one step, skips the second factor on purpose, and survives a password reset
|
|
9
|
+
* (which revokes sessions, not passkeys). So a stolen or borrowed session becomes a durable way in. `stepUp()` puts a
|
|
10
|
+
* recent confirmation in front of `/passkey/generate-register-options` and `/passkey/verify-registration`. What
|
|
11
|
+
* confirms depends on the account:
|
|
12
|
+
*
|
|
13
|
+
* - **two-factor on** → the authenticator code or a backup code, entered now. The client calls Better Auth's own
|
|
14
|
+
* `/two-factor/verify-totp` or `/two-factor/verify-backup-code` with its live session: Better Auth checks the code
|
|
15
|
+
* and creates nothing, and this plugin records a confirmation for that session when the check succeeds. Nothing of
|
|
16
|
+
* the two-factor plugin is copied — no secret, no decryption. Only an account that already had two-factor on
|
|
17
|
+
* before the call counts: on an unenrolled account the same call is the end of enrolment, which proves only that
|
|
18
|
+
* the person holds the authenticator they have just added.
|
|
19
|
+
* - **no two-factor, a password** → the password, at `POST /step-up/password`, checked by Better Auth's own
|
|
20
|
+
* `ctx.context.password.checkPassword`.
|
|
21
|
+
* - **neither** (email link or social only) → a sign-in within the window: the session itself is younger than
|
|
22
|
+
* `windowSeconds`. Nothing to type; the screen asks them to sign in again.
|
|
23
|
+
*
|
|
24
|
+
* A confirmation is a row in Better Auth's `verification` table (`step-up:<session id>`, the user id as its value,
|
|
25
|
+
* expiring after the window). It belongs to that one session, is not used up by the routes it unlocks, and never
|
|
26
|
+
* counts for another session or another user.
|
|
27
|
+
*
|
|
28
|
+
* **Wrong answers.** Better Auth's two-factor lockout and attempt limit apply only to a sign-in; a code checked with a
|
|
29
|
+
* live session is limited by nothing but the rate limiter. So one session gets `maxAttempts` wrong answers, codes and
|
|
30
|
+
* passwords together, before it has to sign in again — and once it has spent them, its live code checks are refused
|
|
31
|
+
* too, so a stolen session cannot keep guessing.
|
|
32
|
+
*
|
|
33
|
+
* Passkey deletion is not gated: removing a way in is not a new one. An app's own administrator second-factor proof
|
|
34
|
+
* (`second-factor-session`) is unchanged and applies on top; either refusal can stop a registration.
|
|
35
|
+
*
|
|
36
|
+
* **Order**: list it after `twoFactor()` and `passkey()`, and before `nextCookies()`.
|
|
37
|
+
*/
|
|
38
|
+
export interface StepUpOptions {
|
|
39
|
+
/** Turn the refusal off for this app (`false`) while keeping the plugin and its endpoints. Default `true`. */
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
/** How long a confirmation lasts, and how young a session must be to count as a recent sign-in. Default 600. */
|
|
42
|
+
windowSeconds?: number;
|
|
43
|
+
/**
|
|
44
|
+
* The routes that need a confirmation. Default {@link STEP_UP_PASSKEY_ROUTES}; to add a route, keep those:
|
|
45
|
+
* `routes: [...STEP_UP_PASSKEY_ROUTES, "/my/route"]`.
|
|
46
|
+
*
|
|
47
|
+
* Only endpoints reached through Better Auth's router have a path to match. A server-only method has none —
|
|
48
|
+
* `auth.api.setPassword` among them — so listing "/set-password" here matches nothing and guards nothing. Check
|
|
49
|
+
* in the caller instead: `auth.api.stepUpStatus({ headers })` before the call, and refuse when not confirmed.
|
|
50
|
+
*/
|
|
51
|
+
routes?: readonly string[];
|
|
52
|
+
/** Wrong answers (codes and passwords together) one session may give before it must sign in again. Default 5. */
|
|
53
|
+
maxAttempts?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Called once a passkey has been added (`/passkey/verify-registration` succeeded) — to email "A passkey was added
|
|
56
|
+
* to your account" with the app's own mailer. It cannot fail or undo the registration: an error is logged and the
|
|
57
|
+
* passkey stays. With Better Auth's `advanced.backgroundTasks` set, it runs in the background.
|
|
58
|
+
*/
|
|
59
|
+
onPasskeyAdded?: (data: PasskeyAddedData) => Promise<void> | void;
|
|
60
|
+
}
|
|
61
|
+
export interface PasskeyAddedData {
|
|
62
|
+
user: User;
|
|
63
|
+
passkey: {
|
|
64
|
+
id: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
createdAt?: Date;
|
|
67
|
+
};
|
|
68
|
+
/** The registration request, when there was one (not for a server-side `auth.api` call). */
|
|
69
|
+
request?: Request;
|
|
70
|
+
}
|
|
71
|
+
export declare function stepUp(options?: StepUpOptions): {
|
|
72
|
+
id: "step-up";
|
|
73
|
+
options: {
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
windowSeconds: number;
|
|
76
|
+
maxAttempts: number;
|
|
77
|
+
routes: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Called once a passkey has been added (`/passkey/verify-registration` succeeded) — to email "A passkey was added
|
|
80
|
+
* to your account" with the app's own mailer. It cannot fail or undo the registration: an error is logged and the
|
|
81
|
+
* passkey stays. With Better Auth's `advanced.backgroundTasks` set, it runs in the background.
|
|
82
|
+
*/
|
|
83
|
+
onPasskeyAdded?: (data: PasskeyAddedData) => Promise<void> | void;
|
|
84
|
+
};
|
|
85
|
+
init(ctx: import("better-auth").AuthContext): void;
|
|
86
|
+
endpoints: {
|
|
87
|
+
stepUpStatus: import("better-auth").StrictEndpoint<"/step-up/status", {
|
|
88
|
+
method: "GET";
|
|
89
|
+
use: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
|
|
90
|
+
session: {
|
|
91
|
+
session: Record<string, any> & {
|
|
92
|
+
id: string;
|
|
93
|
+
createdAt: Date;
|
|
94
|
+
updatedAt: Date;
|
|
95
|
+
userId: string;
|
|
96
|
+
expiresAt: Date;
|
|
97
|
+
token: string;
|
|
98
|
+
ipAddress?: string | null | undefined;
|
|
99
|
+
userAgent?: string | null | undefined;
|
|
100
|
+
};
|
|
101
|
+
user: Record<string, any> & {
|
|
102
|
+
id: string;
|
|
103
|
+
createdAt: Date;
|
|
104
|
+
updatedAt: Date;
|
|
105
|
+
email: string;
|
|
106
|
+
emailVerified: boolean;
|
|
107
|
+
name: string;
|
|
108
|
+
image?: string | null | undefined;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}>>[];
|
|
112
|
+
}, StepUpStatus>;
|
|
113
|
+
stepUpPassword: import("better-auth").StrictEndpoint<"/step-up/password", {
|
|
114
|
+
method: "POST";
|
|
115
|
+
body: z.ZodObject<{
|
|
116
|
+
password: z.ZodString;
|
|
117
|
+
}, z.core.$strip>;
|
|
118
|
+
use: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
|
|
119
|
+
session: {
|
|
120
|
+
session: Record<string, any> & {
|
|
121
|
+
id: string;
|
|
122
|
+
createdAt: Date;
|
|
123
|
+
updatedAt: Date;
|
|
124
|
+
userId: string;
|
|
125
|
+
expiresAt: Date;
|
|
126
|
+
token: string;
|
|
127
|
+
ipAddress?: string | null | undefined;
|
|
128
|
+
userAgent?: string | null | undefined;
|
|
129
|
+
};
|
|
130
|
+
user: Record<string, any> & {
|
|
131
|
+
id: string;
|
|
132
|
+
createdAt: Date;
|
|
133
|
+
updatedAt: Date;
|
|
134
|
+
email: string;
|
|
135
|
+
emailVerified: boolean;
|
|
136
|
+
name: string;
|
|
137
|
+
image?: string | null | undefined;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
}>>[];
|
|
141
|
+
}, {
|
|
142
|
+
confirmed: true;
|
|
143
|
+
confirmedUntil: string;
|
|
144
|
+
locked: false;
|
|
145
|
+
needs: StepUpNeeds;
|
|
146
|
+
sessionFresh: boolean;
|
|
147
|
+
}>;
|
|
148
|
+
};
|
|
149
|
+
hooks: {
|
|
150
|
+
before: {
|
|
151
|
+
matcher: (context: import("better-auth").HookEndpointContext) => boolean;
|
|
152
|
+
handler: import("better-auth/api").AuthMiddleware;
|
|
153
|
+
}[];
|
|
154
|
+
after: {
|
|
155
|
+
matcher: (context: import("better-auth").HookEndpointContext) => boolean;
|
|
156
|
+
handler: import("better-auth/api").AuthMiddleware;
|
|
157
|
+
}[];
|
|
158
|
+
};
|
|
159
|
+
rateLimit: {
|
|
160
|
+
pathMatcher: (path: string) => path is "/step-up/password";
|
|
161
|
+
window: number;
|
|
162
|
+
max: number;
|
|
163
|
+
}[];
|
|
164
|
+
$ERROR_CODES: {
|
|
165
|
+
readonly STEP_UP_REQUIRED: {
|
|
166
|
+
readonly code: "STEP_UP_REQUIRED";
|
|
167
|
+
readonly message: "Confirm it's you before adding a passkey.";
|
|
168
|
+
};
|
|
169
|
+
readonly STEP_UP_LOCKED: {
|
|
170
|
+
readonly code: "STEP_UP_LOCKED";
|
|
171
|
+
readonly message: "Too many wrong attempts. Sign in again, then confirm it's you.";
|
|
172
|
+
};
|
|
173
|
+
readonly STEP_UP_PASSWORD_NOT_ACCEPTED: {
|
|
174
|
+
readonly code: "STEP_UP_PASSWORD_NOT_ACCEPTED";
|
|
175
|
+
readonly message: "This account doesn't confirm with a password.";
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=step-up.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up.d.ts","sourceRoot":"","sources":["../../src/auth/step-up.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,IAAI,EAAE,MAAM,aAAa,CAAC;AAS1D,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAQL,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,aAAa;IAC5B,8GAA8G;IAC9G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gHAAgH;IAChH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,iHAAiH;IACjH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACnE;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,IAAI,CAAA;KAAE,CAAC;IACzD,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwCD,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB;;;;;;;QArDhD;;;;WAIG;yBACc,CAAC,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;;;;;;;;;;;;;;;iCAOhD,CAAC;iCAGoB,CAAC;;;;;;;;;6BAIxB,CAAA;;;;;;;;;;;;;;;;;;;iCAPE,CAAC;iCAGoB,CAAC;;;;;;;;;6BAIxB,CAAA;;;;;;;;;;;;;;;;;;;;;;;4BA4UqB,MAAM;;;;;;;;;;;;;;;;;;EAG3C"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { APIError, createAuthEndpoint, createAuthMiddleware, getAuthoritativeSessionFromCtx, isAPIError, sensitiveSessionMiddleware, } from "better-auth/api";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { DEFAULT_STEP_UP_MAX_ATTEMPTS, DEFAULT_STEP_UP_WINDOW_SECONDS, STEP_UP_CODE_ROUTES, STEP_UP_ERROR_CODES, STEP_UP_ID, STEP_UP_PASSKEY_ROUTES, STEP_UP_REQUIRED_GENERIC_MESSAGE, } from "./step-up-routes.js";
|
|
4
|
+
const confirmationKey = (sessionId) => `${STEP_UP_ID}:${sessionId}`;
|
|
5
|
+
const attemptsKey = (sessionId) => `${STEP_UP_ID}-attempts:${sessionId}`;
|
|
6
|
+
const PASSKEY_VERIFY_REGISTRATION = "/passkey/verify-registration";
|
|
7
|
+
function jsonBody(response) {
|
|
8
|
+
// `ctx.json()` answers the plain object inside hooks, and a `{ _flag: "json", body }` wrapper when the caller
|
|
9
|
+
// asked for a Response; accept either.
|
|
10
|
+
if (response && typeof response === "object" && response._flag === "json") {
|
|
11
|
+
return response.body;
|
|
12
|
+
}
|
|
13
|
+
return response;
|
|
14
|
+
}
|
|
15
|
+
const toDate = (value) => (value instanceof Date ? value : new Date(value));
|
|
16
|
+
function endpointPaths(plugin) {
|
|
17
|
+
return new Set(Object.values(plugin?.endpoints ?? {}).map((endpoint) => endpoint?.path ?? ""));
|
|
18
|
+
}
|
|
19
|
+
export function stepUp(options = {}) {
|
|
20
|
+
const enabled = options.enabled !== false;
|
|
21
|
+
const windowSeconds = options.windowSeconds ?? DEFAULT_STEP_UP_WINDOW_SECONDS;
|
|
22
|
+
const maxAttempts = options.maxAttempts ?? DEFAULT_STEP_UP_MAX_ATTEMPTS;
|
|
23
|
+
const routes = new Set(options.routes ?? STEP_UP_PASSKEY_ROUTES);
|
|
24
|
+
const passkeyRoutes = STEP_UP_PASSKEY_ROUTES;
|
|
25
|
+
const codeRoutes = STEP_UP_CODE_ROUTES;
|
|
26
|
+
if (!Number.isFinite(windowSeconds) || windowSeconds <= 0) {
|
|
27
|
+
throw new Error(`[step-up] windowSeconds must be a positive number of seconds, got ${String(windowSeconds)}.`);
|
|
28
|
+
}
|
|
29
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
30
|
+
throw new Error(`[step-up] maxAttempts must be a whole number of at least 1, got ${String(maxAttempts)}.`);
|
|
31
|
+
}
|
|
32
|
+
/** Code checks under way, keyed by the request's own context object (shared by its before- and after-hooks). */
|
|
33
|
+
const pending = new WeakMap();
|
|
34
|
+
function twoFactorInstalled(context) {
|
|
35
|
+
return (context.options.plugins ?? []).some((plugin) => plugin.id === "two-factor");
|
|
36
|
+
}
|
|
37
|
+
async function needsFor(context, user) {
|
|
38
|
+
if (user.twoFactorEnabled === true && twoFactorInstalled(context))
|
|
39
|
+
return "code";
|
|
40
|
+
// `findAccounts` rather than 1.7's `findCredentialAccount`: 1.6 has only the former.
|
|
41
|
+
const accounts = await context.internalAdapter.findAccounts(user.id);
|
|
42
|
+
if (accounts.some((account) => account.providerId === "credential" && !!account.password))
|
|
43
|
+
return "password";
|
|
44
|
+
return "recent-sign-in";
|
|
45
|
+
}
|
|
46
|
+
async function attemptsUsed(context, sessionId) {
|
|
47
|
+
const row = await context.internalAdapter.findVerificationValue(attemptsKey(sessionId));
|
|
48
|
+
if (!row || toDate(row.expiresAt).getTime() <= Date.now())
|
|
49
|
+
return 0;
|
|
50
|
+
const used = Number(row.value);
|
|
51
|
+
return Number.isInteger(used) && used > 0 ? used : 0;
|
|
52
|
+
}
|
|
53
|
+
async function recordWrongAnswer(context, session) {
|
|
54
|
+
const used = await attemptsUsed(context, session.id);
|
|
55
|
+
await context.internalAdapter.deleteVerificationByIdentifier(attemptsKey(session.id));
|
|
56
|
+
// Kept as long as the session was going to last when it was spent, so the budget does not refill while it lives.
|
|
57
|
+
const lasts = Math.max(toDate(session.expiresAt).getTime(), Date.now() + windowSeconds * 1000);
|
|
58
|
+
await context.internalAdapter.createVerificationValue({
|
|
59
|
+
identifier: attemptsKey(session.id),
|
|
60
|
+
value: String(used + 1),
|
|
61
|
+
expiresAt: new Date(lasts),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function recordConfirmation(context, session) {
|
|
65
|
+
const expiresAt = new Date(Date.now() + windowSeconds * 1000);
|
|
66
|
+
await context.internalAdapter.deleteVerificationByIdentifier(confirmationKey(session.id));
|
|
67
|
+
await context.internalAdapter.createVerificationValue({
|
|
68
|
+
identifier: confirmationKey(session.id),
|
|
69
|
+
value: session.userId,
|
|
70
|
+
expiresAt,
|
|
71
|
+
});
|
|
72
|
+
return expiresAt;
|
|
73
|
+
}
|
|
74
|
+
async function statusFor(context, live) {
|
|
75
|
+
const needs = await needsFor(context, live.user);
|
|
76
|
+
const locked = needs !== "recent-sign-in" && (await attemptsUsed(context, live.session.id)) >= maxAttempts;
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
// The passkey plugin's own freshSessionMiddleware rule, so a screen can say "sign in again" before it asks
|
|
79
|
+
// for a password the passkey routes would then refuse anyway.
|
|
80
|
+
const freshAge = context.sessionConfig.freshAge;
|
|
81
|
+
const sessionFresh = freshAge === 0 || now - toDate(live.session.createdAt).getTime() < freshAge * 1000;
|
|
82
|
+
const row = await context.internalAdapter.findVerificationValue(confirmationKey(live.session.id));
|
|
83
|
+
if (row && row.value === live.session.userId && row.value === live.user.id) {
|
|
84
|
+
const until = toDate(row.expiresAt);
|
|
85
|
+
if (until.getTime() > now) {
|
|
86
|
+
return { needs, confirmed: true, confirmedUntil: until.toISOString(), locked, sessionFresh };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (needs === "recent-sign-in") {
|
|
90
|
+
const until = new Date(toDate(live.session.createdAt).getTime() + windowSeconds * 1000);
|
|
91
|
+
if (until.getTime() > now) {
|
|
92
|
+
return { needs, confirmed: true, confirmedUntil: until.toISOString(), locked, sessionFresh };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { needs, confirmed: false, confirmedUntil: null, locked, sessionFresh };
|
|
96
|
+
}
|
|
97
|
+
/** The session as the session store has it now — never the cookie cache, which can outlive a revoked session. */
|
|
98
|
+
async function liveSession(ctx) {
|
|
99
|
+
const current = (await getAuthoritativeSessionFromCtx(ctx));
|
|
100
|
+
return current?.session ? current : null;
|
|
101
|
+
}
|
|
102
|
+
function passkeyFirstRegistration(context, path) {
|
|
103
|
+
if (!passkeyRoutes.includes(path))
|
|
104
|
+
return false;
|
|
105
|
+
const passkey = (context.options.plugins ?? []).find((plugin) => plugin.id === "passkey");
|
|
106
|
+
return passkey?.options?.registration?.requireSession === false;
|
|
107
|
+
}
|
|
108
|
+
const gate = {
|
|
109
|
+
matcher: (ctx) => enabled && routes.has(ctx.path ?? ""),
|
|
110
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
111
|
+
const live = await liveSession(ctx);
|
|
112
|
+
if (!live) {
|
|
113
|
+
// A passkey-first sign-up (`registration.requireSession: false`) adds a passkey to an account that has no
|
|
114
|
+
// other way in yet: there is nothing to prove, and the passkey plugin's own rules apply.
|
|
115
|
+
if (passkeyFirstRegistration(ctx.context, ctx.path))
|
|
116
|
+
return;
|
|
117
|
+
// Refuse here rather than leave it to the route, whose own check accepts the session cookie cache for a few
|
|
118
|
+
// minutes — long enough for a just-revoked session to slip a passkey through.
|
|
119
|
+
throw new APIError("UNAUTHORIZED", { message: "Unauthorized", code: "UNAUTHORIZED" });
|
|
120
|
+
}
|
|
121
|
+
const status = await statusFor(ctx.context, live);
|
|
122
|
+
if (status.confirmed)
|
|
123
|
+
return;
|
|
124
|
+
throw new APIError("FORBIDDEN", {
|
|
125
|
+
code: STEP_UP_ERROR_CODES.STEP_UP_REQUIRED.code,
|
|
126
|
+
message: passkeyRoutes.includes(ctx.path)
|
|
127
|
+
? STEP_UP_ERROR_CODES.STEP_UP_REQUIRED.message
|
|
128
|
+
: STEP_UP_REQUIRED_GENERIC_MESSAGE,
|
|
129
|
+
needs: status.needs,
|
|
130
|
+
});
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
const beforeCodeCheck = {
|
|
134
|
+
matcher: (ctx) => enabled && codeRoutes.includes(ctx.path ?? ""),
|
|
135
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
136
|
+
const live = await liveSession(ctx);
|
|
137
|
+
// No live session: a sign-in finishing with its code. Two-factor off: enrolment. Neither confirms anything.
|
|
138
|
+
if (!live || live.user.twoFactorEnabled !== true)
|
|
139
|
+
return;
|
|
140
|
+
if ((await attemptsUsed(ctx.context, live.session.id)) >= maxAttempts) {
|
|
141
|
+
throw new APIError("FORBIDDEN", { ...STEP_UP_ERROR_CODES.STEP_UP_LOCKED, needs: "code" });
|
|
142
|
+
}
|
|
143
|
+
pending.set(ctx.context, { session: live.session });
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
const afterCodeCheck = {
|
|
147
|
+
matcher: (ctx) => enabled && codeRoutes.includes(ctx.path ?? ""),
|
|
148
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
149
|
+
const check = pending.get(ctx.context);
|
|
150
|
+
if (!check)
|
|
151
|
+
return;
|
|
152
|
+
pending.delete(ctx.context);
|
|
153
|
+
try {
|
|
154
|
+
const returned = ctx.context.returned;
|
|
155
|
+
if (isAPIError(returned)) {
|
|
156
|
+
// In the live-session branch a 401 means one thing: the code was wrong.
|
|
157
|
+
const wrong = returned.statusCode === 401;
|
|
158
|
+
if (wrong)
|
|
159
|
+
await recordWrongAnswer(ctx.context, check.session);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Better Auth's live-session branch answers with the session it was called with, and creates none. Anything
|
|
163
|
+
// else (a new session, a different token) is not the call this was told about: it proves nothing here.
|
|
164
|
+
const token = jsonBody(returned)?.token;
|
|
165
|
+
if (token !== check.session.token)
|
|
166
|
+
return;
|
|
167
|
+
await recordConfirmation(ctx.context, check.session);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Failing to record only means being asked again — the safe way round — and must not fail the code check.
|
|
171
|
+
ctx.context.logger.error("[step-up] could not record a code check", error);
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
const afterPasskeyAdded = {
|
|
176
|
+
matcher: (ctx) => !!options.onPasskeyAdded && ctx.path === PASSKEY_VERIFY_REGISTRATION,
|
|
177
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
178
|
+
const notify = options.onPasskeyAdded;
|
|
179
|
+
if (!notify || isAPIError(ctx.context.returned))
|
|
180
|
+
return;
|
|
181
|
+
try {
|
|
182
|
+
// Both lines answer with the stored row (1.7 adds `session` and `user` when asked to create a session).
|
|
183
|
+
let row = jsonBody(ctx.context.returned);
|
|
184
|
+
if (typeof row?.id !== "string" || typeof row.userId !== "string") {
|
|
185
|
+
const userId = ctx.context.session?.user?.id;
|
|
186
|
+
if (!userId)
|
|
187
|
+
return;
|
|
188
|
+
const [newest] = await ctx.context.adapter.findMany({
|
|
189
|
+
model: "passkey",
|
|
190
|
+
where: [{ field: "userId", value: userId }],
|
|
191
|
+
sortBy: { field: "createdAt", direction: "desc" },
|
|
192
|
+
limit: 1,
|
|
193
|
+
});
|
|
194
|
+
row = newest;
|
|
195
|
+
}
|
|
196
|
+
if (typeof row?.id !== "string" || typeof row.userId !== "string")
|
|
197
|
+
return;
|
|
198
|
+
const user = await ctx.context.internalAdapter.findUserById(row.userId);
|
|
199
|
+
if (!user)
|
|
200
|
+
return;
|
|
201
|
+
const createdAt = row.createdAt instanceof Date || typeof row.createdAt === "string" ? toDate(row.createdAt) : undefined;
|
|
202
|
+
const passkey = {
|
|
203
|
+
id: row.id,
|
|
204
|
+
...(typeof row.name === "string" && row.name ? { name: row.name } : {}),
|
|
205
|
+
...(createdAt ? { createdAt } : {}),
|
|
206
|
+
};
|
|
207
|
+
const logger = ctx.context.logger;
|
|
208
|
+
const run = (async () => {
|
|
209
|
+
try {
|
|
210
|
+
await notify({ user, passkey, ...(ctx.request ? { request: ctx.request } : {}) });
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
logger.error("[step-up] onPasskeyAdded failed; the passkey was added", error);
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
await ctx.context.runInBackgroundOrAwait(run);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
ctx.context.logger.error("[step-up] could not read the passkey that was added", error);
|
|
220
|
+
}
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
id: STEP_UP_ID,
|
|
225
|
+
options: { ...options, enabled, windowSeconds, maxAttempts, routes: [...routes] },
|
|
226
|
+
init(ctx) {
|
|
227
|
+
if (!enabled)
|
|
228
|
+
return;
|
|
229
|
+
const plugins = ctx.options.plugins ?? [];
|
|
230
|
+
const twoFactor = plugins.find((plugin) => plugin.id === "two-factor");
|
|
231
|
+
if (twoFactor) {
|
|
232
|
+
const paths = endpointPaths(twoFactor);
|
|
233
|
+
const missing = codeRoutes.filter((path) => !paths.has(path));
|
|
234
|
+
if (missing.length) {
|
|
235
|
+
throw new Error(`[step-up] Better Auth's twoFactor() plugin no longer exposes ${missing.join(" and ")}, which is how an ` +
|
|
236
|
+
"account with two-factor on confirms it's them. This Better Auth version changed it — check " +
|
|
237
|
+
"@connextar/house's supported Better Auth range.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (twoFactor?.options?.allowPasswordless) {
|
|
241
|
+
ctx.logger.warn("[step-up] twoFactor({ allowPasswordless: true }) lets a session alone re-key two-factor on an account " +
|
|
242
|
+
"without a password, so for those accounts a code proves no more than the session does.");
|
|
243
|
+
}
|
|
244
|
+
const passkey = plugins.find((plugin) => plugin.id === "passkey");
|
|
245
|
+
if (passkey) {
|
|
246
|
+
const paths = endpointPaths(passkey);
|
|
247
|
+
const missing = passkeyRoutes.filter((path) => !paths.has(path));
|
|
248
|
+
if (missing.length) {
|
|
249
|
+
throw new Error(`[step-up] the passkey plugin no longer registers passkeys at ${missing.join(" and ")}, so the refusal ` +
|
|
250
|
+
"would guard nothing. Check @connextar/house's supported Better Auth range.");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (passkeyRoutes.some((path) => routes.has(path))) {
|
|
254
|
+
ctx.logger.warn("[step-up] no passkey() plugin is installed, so there is no passkey registration to guard.");
|
|
255
|
+
}
|
|
256
|
+
const ownIndex = plugins.findIndex((plugin) => plugin.id === STEP_UP_ID);
|
|
257
|
+
const nextCookiesIndex = plugins.findIndex((plugin) => plugin.id === "next-cookies");
|
|
258
|
+
if (nextCookiesIndex !== -1 && nextCookiesIndex < ownIndex) {
|
|
259
|
+
ctx.logger.warn("[step-up] is listed after nextCookies(); list it before, so the cookies it sets reach Next.");
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
endpoints: {
|
|
263
|
+
stepUpStatus: createAuthEndpoint("/step-up/status", { method: "GET", use: [sensitiveSessionMiddleware] }, async (ctx) => ctx.json(await statusFor(ctx.context, ctx.context.session))),
|
|
264
|
+
stepUpPassword: createAuthEndpoint("/step-up/password", { method: "POST", body: z.object({ password: z.string() }), use: [sensitiveSessionMiddleware] }, async (ctx) => {
|
|
265
|
+
const live = ctx.context.session;
|
|
266
|
+
const needs = await needsFor(ctx.context, live.user);
|
|
267
|
+
if (needs !== "password") {
|
|
268
|
+
throw new APIError("BAD_REQUEST", {
|
|
269
|
+
code: STEP_UP_ERROR_CODES.STEP_UP_PASSWORD_NOT_ACCEPTED.code,
|
|
270
|
+
message: needs === "code"
|
|
271
|
+
? "Enter the code from your authenticator app instead."
|
|
272
|
+
: "This account has no password. Sign in again to confirm it's you.",
|
|
273
|
+
needs,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if ((await attemptsUsed(ctx.context, live.session.id)) >= maxAttempts) {
|
|
277
|
+
throw new APIError("FORBIDDEN", { ...STEP_UP_ERROR_CODES.STEP_UP_LOCKED, needs });
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
// Better Auth's own check: the credential account's hash, its configured verifier, a constant-time
|
|
281
|
+
// answer when there is none, and a 400 INVALID_PASSWORD when it does not match.
|
|
282
|
+
await ctx.context.password.checkPassword(live.user.id, ctx);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (isAPIError(error))
|
|
286
|
+
await recordWrongAnswer(ctx.context, live.session);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
const until = await recordConfirmation(ctx.context, live.session);
|
|
290
|
+
return ctx.json({
|
|
291
|
+
...(await statusFor(ctx.context, live)),
|
|
292
|
+
confirmed: true,
|
|
293
|
+
confirmedUntil: until.toISOString(),
|
|
294
|
+
locked: false,
|
|
295
|
+
});
|
|
296
|
+
}),
|
|
297
|
+
},
|
|
298
|
+
hooks: {
|
|
299
|
+
before: [gate, beforeCodeCheck],
|
|
300
|
+
after: [afterCodeCheck, afterPasskeyAdded],
|
|
301
|
+
},
|
|
302
|
+
// As strict as Better Auth's own two-factor routes: three tries in ten seconds. Better Auth applies it only where
|
|
303
|
+
// its rate limiter is on (production, by default).
|
|
304
|
+
rateLimit: [{ pathMatcher: (path) => path === "/step-up/password", window: 10, max: 3 }],
|
|
305
|
+
$ERROR_CODES: STEP_UP_ERROR_CODES,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=step-up.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up.js","sourceRoot":"","sources":["../../src/auth/step-up.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connextar/house",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "The pieces every app we build needs and none of them should own a copy of.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
"types": "./dist/auth/second-factor-index.d.ts",
|
|
35
35
|
"default": "./dist/auth/second-factor-index.js"
|
|
36
36
|
},
|
|
37
|
+
"./auth/step-up": {
|
|
38
|
+
"types": "./dist/auth/step-up-index.d.ts",
|
|
39
|
+
"default": "./dist/auth/step-up-index.js"
|
|
40
|
+
},
|
|
41
|
+
"./auth/step-up/client": {
|
|
42
|
+
"types": "./dist/auth/step-up-client-index.d.ts",
|
|
43
|
+
"default": "./dist/auth/step-up-client-index.js"
|
|
44
|
+
},
|
|
37
45
|
"./account": {
|
|
38
46
|
"types": "./dist/account/index.d.ts",
|
|
39
47
|
"default": "./dist/account/index.js"
|
|
@@ -158,6 +166,8 @@
|
|
|
158
166
|
},
|
|
159
167
|
"devDependencies": {
|
|
160
168
|
"@anthropic-ai/sdk": "^0.128.0",
|
|
169
|
+
"@better-auth/core": "1.7.2",
|
|
170
|
+
"@better-auth/passkey": "1.7.2",
|
|
161
171
|
"@eslint/js": "^9.39.5",
|
|
162
172
|
"@testing-library/react": "^16.3.3",
|
|
163
173
|
"@types/node": "^26.4.1",
|
|
@@ -167,6 +177,7 @@
|
|
|
167
177
|
"@typescript-eslint/parser": "^8.0.0",
|
|
168
178
|
"better-auth": "1.7.2",
|
|
169
179
|
"better-auth-1-6": "npm:better-auth@1.6.33",
|
|
180
|
+
"better-auth-passkey-1-6": "npm:@better-auth/passkey@1.6.33",
|
|
170
181
|
"eslint": "^9.0.0",
|
|
171
182
|
"jsdom": "^27.4.0",
|
|
172
183
|
"next": "^16.3.3",
|