@stacksjs/defaults 0.75.181 → 0.75.182
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/ai/skills/stacks-auth/SKILL.md +37 -12
- package/ai/skills/stacks-native/CATALOG.md +3 -3
- package/app/Actions/Auth/PasskeyAuthenticationAction.ts +23 -0
- package/app/Actions/Auth/PasskeyAuthenticationOptionsAction.ts +17 -0
- package/app/Actions/Auth/PasskeyDestroyAction.ts +24 -0
- package/app/Actions/Auth/PasskeyIndexAction.ts +18 -0
- package/app/Actions/Auth/PasskeyRegistrationAction.ts +20 -0
- package/app/Actions/Auth/PasskeyRegistrationOptionsAction.ts +18 -0
- package/app/Actions/Auth/passkey-config.ts +13 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/routes/auth.ts +9 -0
|
@@ -337,18 +337,43 @@ const result = await actions.resetPassword(token, newPassword)
|
|
|
337
337
|
- `logout(): Promise<void>`
|
|
338
338
|
- `refresh(): Promise<void>`
|
|
339
339
|
|
|
340
|
-
## Passkey/WebAuthn
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
- `
|
|
349
|
-
-
|
|
350
|
-
- `
|
|
351
|
-
|
|
340
|
+
## Passkey/WebAuthn
|
|
341
|
+
|
|
342
|
+
Opt in with `config.auth.passkeys: { enabled: true, origin?: string, name?: string }`.
|
|
343
|
+
The canonical origin defaults to `config.app.url`; production requires HTTPS.
|
|
344
|
+
`buddy migrate` / `buddy auth:setup` create `passkey_ceremonies` and `passkeys`.
|
|
345
|
+
|
|
346
|
+
- `beginPasskeyRegistration({ id, email?, name? }, { origin, name })` returns
|
|
347
|
+
`{ ceremony, options }` as JSON. Call only for the authenticated account.
|
|
348
|
+
- `finishPasskeyRegistration(userId, ceremony, credentialJSON, party)` consumes
|
|
349
|
+
the account-bound challenge and saves only verified resident credentials.
|
|
350
|
+
- `beginPasskeyAuthentication(party)` returns discoverable options without an
|
|
351
|
+
email lookup, usable with conditional mediation and `autocomplete="username webauthn"`.
|
|
352
|
+
- `finishPasskeyAuthentication(ceremony, credentialJSON, party)` returns the
|
|
353
|
+
verified user ID; the application then issues its usual session.
|
|
354
|
+
- `listPasskeys(userId)` returns display-safe metadata, never public-key material.
|
|
355
|
+
- `removePasskey(userId, id, hasAlternativeSignIn?)` serializes removals and
|
|
356
|
+
refuses to remove the last key without another login method. Supply a callback
|
|
357
|
+
when password hashes may be unusable placeholders or other factors exist.
|
|
358
|
+
|
|
359
|
+
Default routes under `/auth/passkeys` expose registration/options, registration,
|
|
360
|
+
authentication/options, authentication (POST), listing (GET) and deletion (DELETE
|
|
361
|
+
`/{id}`). Public authentication endpoints require the canonical Origin header;
|
|
362
|
+
enrollment/removal also require auth and CSRF protection. Deleting the last key
|
|
363
|
+
requires proving the fallback password in the request. All responses are no-store.
|
|
364
|
+
|
|
365
|
+
Browser code imports **`@stacksjs/auth/passkeys-browser`**, never the server barrel.
|
|
366
|
+
It exports `startRegistration`, `startAuthentication(options, { signal?, mediation? })`,
|
|
367
|
+
`browserSupportsWebAuthn`, `browserSupportsWebAuthnAutofill`, the JSON option/credential
|
|
368
|
+
codecs and types. Abort a pending conditional request before explicit sign-in or
|
|
369
|
+
password submission, and on unmount. Serialize results using
|
|
370
|
+
`registrationCredentialToJSON` / `authenticationCredentialToJSON` before POSTing.
|
|
371
|
+
Native JS contexts require a platform passkey bridge; web support alone does not
|
|
372
|
+
provide one. Keep existing native sign-in available until that bridge exists.
|
|
373
|
+
|
|
374
|
+
Legacy helpers (`getUserPasskeys`, `getUserPasskey`, `storeWebAuthnChallenge`,
|
|
375
|
+
`consumeWebAuthnChallenge`) remain for existing integrations. New flows should
|
|
376
|
+
use the ceremony API, which supports concurrent anonymous browser attempts.
|
|
352
377
|
|
|
353
378
|
## Auth Middleware (middleware.ts)
|
|
354
379
|
|
|
@@ -18,7 +18,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
18
18
|
| `@stacksjs/api` | [`stacks-api`](../stacks-api/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/api/src/index.ts) | 7 |
|
|
19
19
|
| `@stacksjs/arrays` | [`stacks-arrays`](../stacks-arrays/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/arrays/src/index.ts) | 6 |
|
|
20
20
|
| `@stacksjs/audio` | [`stacks-audio`](../stacks-audio/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/audio/src/index.ts) | 2 |
|
|
21
|
-
| `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) |
|
|
21
|
+
| `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 113 |
|
|
22
22
|
| `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 8 |
|
|
23
23
|
| `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 7 |
|
|
24
24
|
| `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 145 |
|
|
@@ -183,9 +183,9 @@ Guidance: [`stacks-auth`](../stacks-auth/SKILL.md). Source: [storage/framework/c
|
|
|
183
183
|
|
|
184
184
|
Declared runtime exports: `DEFAULT_ROLE_PACKS`, `createBqbRbacStore`, `generateTOTP`, `generateTOTPSecret`, `seedDefaultRoles`, `totpKeyUri`, `verifyTOTP`.
|
|
185
185
|
|
|
186
|
-
Local source modules: [src/authentication.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authentication.ts), [src/authenticator.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authenticator.ts), [src/authorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authorizable.ts), [src/browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session.ts), [src/client.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/client.ts), [src/cookie-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/cookie-auth.ts), [src/credential-user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/credential-user.ts), [src/email-verification.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/email-verification.ts), [src/gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/gate.ts), [src/magic-link.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/magic-link.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/middleware.ts), [src/oauth-authorization-codes.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-codes.ts), [src/oauth-authorization-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-endpoint.ts), [src/oauth-authorization-requests.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-requests.ts), [src/oauth-authorization-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-response.ts), [src/oauth-authorization.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization.ts), [src/oauth-browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-browser-session.ts), [src/oauth-client-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-client-registration.ts), [src/oauth-consent.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-consent.ts), [src/oauth-delegated-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-delegated-access.ts), [src/oauth-grants.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-grants.ts), [src/oauth-introspection.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-introspection.ts), [src/oauth-metadata.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-metadata.ts), [src/oauth-pkce.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pkce.ts), [src/oauth-provider.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-provider.ts), [src/oauth-pruning.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pruning.ts), [src/oauth-revocation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-revocation.ts), [src/oauth-token-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-endpoint.ts), [src/oauth-token-exchange.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-exchange.ts), [src/oauth-token-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-request.ts), [src/oauth-token-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-response.ts), [src/page-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/page-gate.ts), [src/passkey.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkey.ts), [src/password/reset.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset.ts), [src/permissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/permissions.ts), [src/policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/policy.ts), [src/rate-limiter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rate-limiter.ts), [src/rbac-seed.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-seed.ts), [src/rbac-store-bqb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-store-bqb.ts), [src/rbac.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac.ts), [src/referrals.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/referrals.ts), [src/register.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/register.ts), [src/request-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/request-token.ts), [src/session-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-auth.ts), [src/socials.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/socials.ts), [src/team.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/team.ts), [src/tokens.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/tokens.ts), [src/two-factor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/two-factor.ts), [src/user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/user.ts).
|
|
186
|
+
Local source modules: [src/authentication.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authentication.ts), [src/authenticator.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authenticator.ts), [src/authorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authorizable.ts), [src/browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session.ts), [src/client.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/client.ts), [src/cookie-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/cookie-auth.ts), [src/credential-user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/credential-user.ts), [src/email-verification.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/email-verification.ts), [src/gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/gate.ts), [src/magic-link.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/magic-link.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/middleware.ts), [src/oauth-authorization-codes.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-codes.ts), [src/oauth-authorization-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-endpoint.ts), [src/oauth-authorization-requests.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-requests.ts), [src/oauth-authorization-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-response.ts), [src/oauth-authorization.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization.ts), [src/oauth-browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-browser-session.ts), [src/oauth-client-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-client-registration.ts), [src/oauth-consent.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-consent.ts), [src/oauth-delegated-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-delegated-access.ts), [src/oauth-grants.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-grants.ts), [src/oauth-introspection.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-introspection.ts), [src/oauth-metadata.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-metadata.ts), [src/oauth-pkce.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pkce.ts), [src/oauth-provider.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-provider.ts), [src/oauth-pruning.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pruning.ts), [src/oauth-revocation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-revocation.ts), [src/oauth-token-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-endpoint.ts), [src/oauth-token-exchange.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-exchange.ts), [src/oauth-token-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-request.ts), [src/oauth-token-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-response.ts), [src/page-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/page-gate.ts), [src/passkey.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkey.ts), [src/passkeys.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkeys.ts), [src/password/reset.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset.ts), [src/permissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/permissions.ts), [src/policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/policy.ts), [src/rate-limiter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rate-limiter.ts), [src/rbac-seed.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-seed.ts), [src/rbac-store-bqb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-store-bqb.ts), [src/rbac.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac.ts), [src/referrals.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/referrals.ts), [src/register.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/register.ts), [src/request-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/request-token.ts), [src/session-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-auth.ts), [src/socials.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/socials.ts), [src/team.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/team.ts), [src/tokens.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/tokens.ts), [src/two-factor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/two-factor.ts), [src/user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/user.ts).
|
|
187
187
|
|
|
188
|
-
Evidence directory: [
|
|
188
|
+
Evidence directory: [113 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
189
189
|
|
|
190
190
|
### @stacksjs/browser
|
|
191
191
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions/runtime'
|
|
2
|
+
import { finishPasskeyAuthentication, Auth, resolveBrowserSessionPolicy, authCookieForBrowserSession } from '@stacksjs/auth'
|
|
3
|
+
import { passkeyParty, passkeyFailure } from './passkey-config'
|
|
4
|
+
|
|
5
|
+
export default new Action({
|
|
6
|
+
name: 'PasskeyAuthentication',
|
|
7
|
+
method: 'POST',
|
|
8
|
+
async handle(request) {
|
|
9
|
+
const party = passkeyParty()
|
|
10
|
+
if (!party) return new Response(null, { status: 404 })
|
|
11
|
+
if (request.headers.get('origin') !== new URL(party.origin).origin) return new Response(null, { status: 403 })
|
|
12
|
+
try {
|
|
13
|
+
const body = request.all()
|
|
14
|
+
const id = await finishPasskeyAuthentication(body.ceremony, body.credential, party)
|
|
15
|
+
const policy = resolveBrowserSessionPolicy(body.remember)
|
|
16
|
+
const session = await Auth.loginUsingId(id, { expiresInMinutes: policy.expiresInMinutes, withRefreshToken: policy.withRefreshToken })
|
|
17
|
+
if (!session) return new Response(null, { status: 401 })
|
|
18
|
+
return Response.json({ token: session.token, user: { id: session.user?.id, email: session.user?.email, name: session.user?.name }, expires_in: session.expiresIn, remembered: policy.remembered },
|
|
19
|
+
{ headers: { 'Cache-Control': 'no-store', 'Set-Cookie': authCookieForBrowserSession(session.token, session.expiresIn) } })
|
|
20
|
+
}
|
|
21
|
+
catch (error) { return passkeyFailure(error) }
|
|
22
|
+
},
|
|
23
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions/runtime'
|
|
2
|
+
import { beginPasskeyAuthentication } from '@stacksjs/auth'
|
|
3
|
+
import { passkeyParty, passkeyFailure, passkeyJSON } from './passkey-config'
|
|
4
|
+
|
|
5
|
+
export default new Action({
|
|
6
|
+
name: 'PasskeyAuthenticationOptions',
|
|
7
|
+
method: 'POST',
|
|
8
|
+
async handle(request) {
|
|
9
|
+
const party = passkeyParty()
|
|
10
|
+
if (!party) return new Response(null, { status: 404 })
|
|
11
|
+
if (request.headers.get('origin') !== new URL(party.origin).origin) return new Response(null, { status: 403 })
|
|
12
|
+
try {
|
|
13
|
+
return passkeyJSON(await beginPasskeyAuthentication(party))
|
|
14
|
+
}
|
|
15
|
+
catch (error) { return passkeyFailure(error) }
|
|
16
|
+
},
|
|
17
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions/runtime'
|
|
2
|
+
import { verifyHash } from '@stacksjs/security'
|
|
3
|
+
import { removePasskey } from '@stacksjs/auth'
|
|
4
|
+
import { passkeyParty, passkeyFailure, passkeyJSON } from './passkey-config'
|
|
5
|
+
|
|
6
|
+
export default new Action({
|
|
7
|
+
name: 'PasskeyDestroy',
|
|
8
|
+
method: 'DELETE',
|
|
9
|
+
async handle(request) {
|
|
10
|
+
const party = passkeyParty()
|
|
11
|
+
if (!party) return new Response(null, { status: 404 })
|
|
12
|
+
try {
|
|
13
|
+
const user = await request.user()
|
|
14
|
+
if (!user) return new Response(null, { status: 401 })
|
|
15
|
+
const id = request.getParam('id')
|
|
16
|
+
if (typeof id !== 'string' || !/^[A-Za-z0-9_-]{1,1366}$/.test(id)) return new Response(null, { status: 422 })
|
|
17
|
+
return await removePasskey(Number(user.id), id, async row => {
|
|
18
|
+
const password = request.get('password')
|
|
19
|
+
return typeof password === 'string' && !!password && typeof row.password === 'string' && await verifyHash(password, row.password)
|
|
20
|
+
}) ? passkeyJSON({ ok: true }) : new Response(null, { status: 404 })
|
|
21
|
+
}
|
|
22
|
+
catch (error) { return passkeyFailure(error) }
|
|
23
|
+
},
|
|
24
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions/runtime'
|
|
2
|
+
import { listPasskeys } from '@stacksjs/auth'
|
|
3
|
+
import { passkeyParty, passkeyFailure, passkeyJSON } from './passkey-config'
|
|
4
|
+
|
|
5
|
+
export default new Action({
|
|
6
|
+
name: 'PasskeyIndex',
|
|
7
|
+
method: 'GET',
|
|
8
|
+
async handle(request) {
|
|
9
|
+
const party = passkeyParty()
|
|
10
|
+
if (!party) return new Response(null, { status: 404 })
|
|
11
|
+
try {
|
|
12
|
+
const user = await request.user()
|
|
13
|
+
if (!user) return new Response(null, { status: 401 })
|
|
14
|
+
return passkeyJSON({ passkeys: await listPasskeys(Number(user.id)) })
|
|
15
|
+
}
|
|
16
|
+
catch (error) { return passkeyFailure(error) }
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions/runtime'
|
|
2
|
+
import { finishPasskeyRegistration } from '@stacksjs/auth'
|
|
3
|
+
import { passkeyParty, passkeyFailure, passkeyJSON } from './passkey-config'
|
|
4
|
+
|
|
5
|
+
export default new Action({
|
|
6
|
+
name: 'PasskeyRegistration',
|
|
7
|
+
method: 'POST',
|
|
8
|
+
async handle(request) {
|
|
9
|
+
const party = passkeyParty()
|
|
10
|
+
if (!party) return new Response(null, { status: 404 })
|
|
11
|
+
try {
|
|
12
|
+
const user = await request.user()
|
|
13
|
+
if (!user) return new Response(null, { status: 401 })
|
|
14
|
+
const body = request.all()
|
|
15
|
+
await finishPasskeyRegistration(Number(user.id), body.ceremony, body.credential, party)
|
|
16
|
+
return passkeyJSON({ ok: true })
|
|
17
|
+
}
|
|
18
|
+
catch (error) { return passkeyFailure(error) }
|
|
19
|
+
},
|
|
20
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions/runtime'
|
|
2
|
+
import { beginPasskeyRegistration } from '@stacksjs/auth'
|
|
3
|
+
import { passkeyParty, passkeyFailure, passkeyJSON } from './passkey-config'
|
|
4
|
+
|
|
5
|
+
export default new Action({
|
|
6
|
+
name: 'PasskeyRegistrationOptions',
|
|
7
|
+
method: 'POST',
|
|
8
|
+
async handle(request) {
|
|
9
|
+
const party = passkeyParty()
|
|
10
|
+
if (!party) return new Response(null, { status: 404 })
|
|
11
|
+
try {
|
|
12
|
+
const user = await request.user()
|
|
13
|
+
if (!user) return new Response(null, { status: 401 })
|
|
14
|
+
return passkeyJSON(await beginPasskeyRegistration({ id: Number(user.id), email: user.email, name: user.name }, party))
|
|
15
|
+
}
|
|
16
|
+
catch (error) { return passkeyFailure(error) }
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { config } from '@stacksjs/config'
|
|
2
|
+
import { PasskeyError } from '@stacksjs/auth'
|
|
3
|
+
export function passkeyParty() {
|
|
4
|
+
const options = config.auth.passkeys
|
|
5
|
+
return options?.enabled ? { origin: options.origin || config.app.url || 'http://localhost:3333', name: options.name || config.app.name || 'Stacks' } : null
|
|
6
|
+
}
|
|
7
|
+
export function passkeyFailure(error: unknown): Response {
|
|
8
|
+
if (!(error instanceof PasskeyError)) throw error
|
|
9
|
+
return Response.json({ error: error.message }, { status: 400, headers: { 'Cache-Control': 'no-store' } })
|
|
10
|
+
}
|
|
11
|
+
export function passkeyJSON(value: unknown): Response {
|
|
12
|
+
return Response.json(value, { headers: { 'Cache-Control': 'no-store' } })
|
|
13
|
+
}
|
package/ide/vscode/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"publisher": "Stacks",
|
|
3
3
|
"name": "vscode-stacks",
|
|
4
4
|
"displayName": "Stacks",
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.182",
|
|
6
6
|
"description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.182",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/f7": "^1.2.2",
|
|
57
57
|
"@iconify-json/hugeicons": "^1.2.35",
|
|
58
|
-
"@stacksjs/mobile": "0.75.
|
|
58
|
+
"@stacksjs/mobile": "0.75.182",
|
|
59
59
|
"@stacksjs/sanitizer": "^0.2.483",
|
|
60
60
|
"@xterm/addon-fit": "^0.11.0",
|
|
61
61
|
"@xterm/xterm": "^6.0.0",
|
package/routes/auth.ts
CHANGED
|
@@ -121,3 +121,12 @@ route.group({ prefix: '/password' }, () => {
|
|
|
121
121
|
route.post('/reset', 'Actions/Password/PasswordResetAction').rateLimit(5, 'minute')
|
|
122
122
|
route.post('/verify-token', 'Actions/Password/VerifyResetTokenAction').rateLimit(10, 'minute')
|
|
123
123
|
})
|
|
124
|
+
|
|
125
|
+
// Discoverable passkeys: canonical JSON ceremonies, independent concurrent browser attempts.
|
|
126
|
+
// Public flows carry a one-use ceremony capability; enrollment and removal also require auth/CSRF.
|
|
127
|
+
route.post('/auth/passkeys/authentication/options', 'Actions/Auth/PasskeyAuthenticationOptionsAction').skipCsrf().rateLimit(10, 'minute')
|
|
128
|
+
route.post('/auth/passkeys/authentication', 'Actions/Auth/PasskeyAuthenticationAction').skipCsrf().rateLimit(10, 'minute')
|
|
129
|
+
route.post('/auth/passkeys/registration/options', 'Actions/Auth/PasskeyRegistrationOptionsAction').middleware('auth').rateLimit(5, 'minute')
|
|
130
|
+
route.post('/auth/passkeys/registration', 'Actions/Auth/PasskeyRegistrationAction').middleware('auth').rateLimit(5, 'minute')
|
|
131
|
+
route.get('/auth/passkeys', 'Actions/Auth/PasskeyIndexAction').middleware('auth')
|
|
132
|
+
route.delete('/auth/passkeys/{id}', 'Actions/Auth/PasskeyDestroyAction').middleware('auth').rateLimit(10, 'minute')
|