@puzzle-ui/auth 0.5.0 → 0.5.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.
Files changed (2) hide show
  1. package/README.md +32 -279
  2. package/package.json +8 -3
package/README.md CHANGED
@@ -1,302 +1,55 @@
1
- ## Multi-factor authentication, passkeys and step-up
1
+ # @puzzle-ui/auth
2
2
 
3
- The authentication package includes a framework-independent MFA implementation.
3
+ Framework-independent authentication primitives for Puzzle applications. Use this package when you need server-side registration and password sign-in, session validation, email and password lifecycle flows, MFA, passkeys, recovery codes, or step-up authentication without tying your auth core to a framework.
4
4
 
5
- Supported authentication factors:
5
+ **Documentation:** [puzzle-ui.com/docs/auth](https://puzzle-ui.com/docs/auth)
6
6
 
7
- - TOTP authenticators.
8
- - WebAuthn passkeys.
9
- - Single-use recovery codes.
7
+ ## Install
10
8
 
11
- The MFA manager supports:
12
-
13
- - TOTP factor enrollment and verification.
14
- - Passkey registration and authentication.
15
- - Discoverable passkey sign-in.
16
- - Password-backed MFA enrollment.
17
- - Mandatory first-factor enrollment.
18
- - Recovery code generation, verification and regeneration.
19
- - Factor listing and removal.
20
- - Challenge expiration and attempt limits.
21
- - TOTP replay protection.
22
- - WebAuthn challenge, origin and RP ID verification.
23
- - Authenticator counter validation.
24
- - MFA audit events.
25
- - MFA-specific rate limiting.
26
-
27
- ### MFA policy
9
+ ```bash
10
+ npm install @puzzle-ui/auth
11
+ ```
28
12
 
29
- MFA is optional by default.
13
+ The package uses `@puzzle-ui/storage` and `@puzzle-ui/session` adapters, and includes a SimpleWebAuthn server adapter. You provide a storage adapter, a session manager, a secure Argon2id `PasswordHasher`, a shared `RateLimitProvider`, and an email provider for lifecycle workflows.
30
14
 
31
- Configure a global requirement through `AuthManagerOptions`:
15
+ ## Minimal usage
32
16
 
33
17
  ```ts
34
- const auth = new StorageAuthManager(
35
- {
36
- storage,
37
- passwordHasher,
38
- rateLimitProvider,
39
- sessions,
40
- mfa,
41
- },
42
- {
43
- rateLimitKeySecret,
44
- mfaPolicy: {
45
- required: true,
46
- },
47
- },
48
- );
49
- ```
18
+ import { StorageAuthManager } from "@puzzle-ui/auth";
50
19
 
51
- For user-specific requirements, configure `requiredFor`:
52
-
53
- ```ts
54
20
  const auth = new StorageAuthManager(
55
- {
56
- storage,
57
- passwordHasher,
58
- rateLimitProvider,
59
- sessions,
60
- mfa,
61
- },
62
- {
63
- rateLimitKeySecret,
64
- mfaPolicy: {
65
- requiredFor: async (user) => {
66
- const account = await roleRepository.findByUserId(user.id);
67
-
68
- return account?.role === "admin";
69
- },
70
- },
71
- },
21
+ { storage, passwordHasher, rateLimitProvider, sessions },
22
+ { rateLimitKeySecret },
72
23
  );
73
- ```
74
-
75
- The callback receives the public `AuthUser`. It can return a boolean or a promise.
76
-
77
- When neither `required` nor `requiredFor` requires MFA, users without enrolled factors can sign in normally. Existing active factors are still challenged during sign-in.
78
-
79
- ### Mandatory first-factor enrollment
80
-
81
- When MFA is required and the user has no active factor:
82
-
83
- 1. Password authentication succeeds.
84
- 2. The manager creates an enrollment challenge.
85
- 3. No normal authenticated session is issued.
86
- 4. The user enrolls either a TOTP authenticator or a passkey.
87
- 5. The first factor is verified.
88
- 6. Recovery codes are generated.
89
- 7. The factor, recovery codes and challenge consumption are committed.
90
- 8. Only then is an authenticated session issued.
91
-
92
- The temporary enrollment token is server-only and must be transported through a short-lived, secure, HttpOnly cookie.
93
-
94
- The browser must never receive the raw challenge token in JSON, local storage or session storage.
95
-
96
- The authentication state is `mfa-enrollment-pending` until the enrollment has completed.
97
-
98
- ### Existing sessions and policy changes
99
-
100
- The MFA policy is evaluated when an existing session is inspected.
101
-
102
- If a policy now requires MFA and the user has no active factor, the authentication state becomes `mfa-enrollment-required`.
103
-
104
- This is a restricted state, not an authenticated application session:
105
24
 
106
- - `getCurrentUser()` returns `null`.
107
- - `getAuthState()` returns `mfa-enrollment-required`.
108
- - Applications must not authorize protected operations for this state.
109
- - The user can use the enrollment flow after confirming their current password.
110
- - Normal authentication is restored after an active factor exists.
25
+ await auth.initialize();
111
26
 
112
- Removing the last active factor therefore cannot silently preserve full application access when MFA is required.
113
-
114
- ### TOTP enrollment
115
-
116
- For an authenticated user with a normal session:
117
-
118
- ```ts
119
- const enrollment = await mfa.beginTotpEnrollment({
120
- sessionToken,
121
- currentPassword,
122
- ipAddress,
123
- label: "Authenticator",
27
+ const result = await auth.register({
28
+ email: "person@example.com",
29
+ password: "a-long-unique-password",
30
+ ipAddress: trustedClientIp,
124
31
  });
125
- ```
126
-
127
- Display the returned `otpauthUrl` as a QR code or allow the user to enter the secret manually.
128
-
129
- Verify the first code:
130
-
131
- ```ts
132
- const completed = await mfa.verifyTotpEnrollment({
133
- sessionToken,
134
- factorId: enrollment.factorId,
135
- code,
136
- ipAddress,
137
- });
138
- ```
139
-
140
- The result includes the public factor and recovery codes. Display recovery codes once and ask the user to store them securely.
141
-
142
- ### Passkey enrollment
143
-
144
- Passkey registration is a two-step WebAuthn ceremony:
145
-
146
- 1. Generate registration options.
147
- 2. Verify the browser's registration response on the server.
148
-
149
- The server must validate the expected challenge, origin, RP ID and user verification result.
150
-
151
- Never trust browser-provided credential metadata without server-side verification.
152
-
153
- ### MFA sign-in
154
-
155
- Password-backed sign-in returns `mfa-pending` when the account has active factors.
156
-
157
- The user can complete the challenge using:
158
-
159
- - TOTP.
160
- - A registered passkey.
161
- - A recovery code.
162
32
 
163
- Discoverable passkey sign-in is also supported without requiring an email and password first.
164
-
165
- A successful MFA challenge creates the authenticated session. The challenge is single-use and expires after its configured lifetime.
166
-
167
- ### Recovery codes
168
-
169
- Recovery codes are generated during successful factor enrollment.
170
-
171
- Store only their hashes. A recovery code:
172
-
173
- - Can be used once.
174
- - Is consumed atomically with successful authentication.
175
- - Must never be written to audit metadata or application logs.
176
-
177
- Users can regenerate their recovery codes after confirming their current password. Regeneration invalidates the previous unused codes.
178
-
179
- ### Step-up authentication
180
-
181
- The step-up manager requires a recent MFA verification for sensitive operations.
182
-
183
- Supported methods:
184
-
185
- - TOTP.
186
- - Passkey.
187
- - Recovery code.
188
-
189
- A step-up challenge is bound to the current authenticated session. Successful verification rotates that session and records the authentication method and verification timestamp in session metadata.
190
-
191
- Use `hasRecentStepUp()` or `requireRecentStepUp()` in server-side actions that require recent MFA verification.
192
-
193
- ```ts
194
- const session = await requireRecentStepUp(request, sessionOptions, 300);
33
+ if (!("status" in result)) {
34
+ // Store result.token in a secure, HttpOnly cookie on the server.
35
+ }
195
36
  ```
196
37
 
197
- The default maximum age is five minutes. The accepted maximum is one hour.
198
-
199
- ### MFA configuration
200
-
201
- `AuthMfaManagerOptions` supports:
202
-
203
- | Option | Purpose |
204
- | -------------------------- | ------------------------------------------------ |
205
- | `rateLimitKeySecret` | HMAC secret for rate-limit identifiers |
206
- | `factorsCollection` | MFA factor records |
207
- | `challengesCollection` | MFA challenge records |
208
- | `recoveryCodesCollection` | Hashed recovery codes |
209
- | `challengeLifetimeSeconds` | Challenge lifetime |
210
- | `recoveryCodeCount` | Number of recovery codes generated |
211
- | `issuer` | TOTP issuer displayed in authenticator apps |
212
- | `totp` | TOTP digits, period and verification window |
213
- | `webAuthn` | RP ID, RP name and expected origin |
214
- | `requireVerifiedEmail` | Require verified email before MFA authentication |
215
- | `clock` | Injectable clock for deterministic tests |
216
-
217
- WebAuthn must use an exact expected origin and RP ID. Production deployments must use HTTPS.
218
-
219
- TOTP secrets must be protected using a configured `AuthSecretProtector`. Do not store plaintext TOTP secrets.
220
-
221
- ### Fullstack integration
222
-
223
- `@puzzle-ui/fullstack` provides framework-independent server handlers and a browser client.
224
-
225
- Use the explicit package entry points:
226
-
227
- - `@puzzle-ui/fullstack/auth/server`
228
- - `@puzzle-ui/fullstack/auth/client`
229
- - `@puzzle-ui/fullstack/auth/shared`
230
- - `@puzzle-ui/fullstack/auth/next`
231
- - `@puzzle-ui/fullstack/auth/tanstack`
232
-
233
- The server owns cookies, challenge tokens and session tokens. The browser client receives public state and factor data only.
234
-
235
- The client authentication statuses include:
236
-
237
- - `unauthenticated`
238
- - `mfa-pending`
239
- - `mfa-enrollment-pending`
240
- - `mfa-enrollment-required`
241
- - `step-up-pending`
242
- - `authenticated`
243
-
244
- Applications must treat only `authenticated` as normal authenticated access.
245
-
246
- ### MFA security requirements
247
-
248
- Production deployments must:
249
-
250
- 1. Use HTTPS.
251
- 2. Keep session and challenge tokens in secure, HttpOnly cookies.
252
- 3. Keep TOTP secrets encrypted at rest.
253
- 4. Keep recovery codes hashed at rest.
254
- 5. Configure exact WebAuthn origins and RP IDs.
255
- 6. Use shared, persistent rate limiting.
256
- 7. Preserve conditional writes and atomic storage operations.
257
- 8. Reject expired, consumed and over-attempted challenges.
258
- 9. Never expose tokens, TOTP secrets, recovery codes or provider errors in logs.
259
- 10. Restrict application access when a required MFA factor is missing.
260
-
261
- ### MFA testing
262
-
263
- Run the authentication package tests:
264
-
265
- ```sh
266
- bun run --filter @puzzle-ui/auth test
267
- ```
268
-
269
- Run the fullstack tests:
270
-
271
- ```sh
272
- bun run --filter @puzzle-ui/fullstack test
273
- ```
274
-
275
- Run typechecks:
276
-
277
- ```sh
278
- bun run --filter @puzzle-ui/auth typecheck
279
- bun run --filter @puzzle-ui/fullstack typecheck
280
- ```
281
-
282
- Build both packages:
283
-
284
- ```sh
285
- bun run --filter @puzzle-ui/auth build
286
- bun run --filter @puzzle-ui/fullstack build
287
- ```
38
+ Keep session and challenge tokens server-side. The token must not be sent in browser JSON or stored in local or session storage. Add the MFA manager to the dependencies and configure `mfaPolicy` when MFA is needed.
288
39
 
289
- ## Scope and roadmap
40
+ ## Features and entry point
290
41
 
291
- `@puzzle-ui/auth` provides the framework-independent authentication core, account lifecycle workflows, MFA, passkeys, recovery codes and step-up authentication.
42
+ The package exports all APIs from the single root entry point, `@puzzle-ui/auth`:
292
43
 
293
- `@puzzle-ui/fullstack` provides the browser client, server handlers and Next.js and TanStack Start integration entry points.
44
+ - `StorageAuthManager` for registration, password sign-in, auth state and session revocation.
45
+ - `StorageAuthLifecycleManager` for email verification/change, password reset/change, profile updates, account deactivation and deletion.
46
+ - `StorageAuthMfaManager` for TOTP, WebAuthn passkeys, recovery codes and factor management.
47
+ - `StorageAuthStepUpManager` for session-bound additional MFA verification and session rotation.
48
+ - `StorageRateLimitProvider`, `WebCryptoAuthSecretProtector`, and `SimpleWebAuthnProvider` as built-in infrastructure adapters.
49
+ - Typed provider contracts, storage records and collection setup, auth errors, and crypto/TOTP utilities.
294
50
 
295
- The following features remain outside the current authentication scope:
51
+ For storage adapter capabilities and initialization, see [`@puzzle-ui/storage`](https://puzzle-ui.com/docs/storage). For browser clients, secure cookie handlers, and framework adapters, see [`@puzzle-ui/fullstack`](https://puzzle-ui.com/docs/fullstack). The [Authentication Setup guide](https://puzzle-ui.com/docs/developer/authentication) describes application integration.
296
52
 
297
- - OAuth and external identity providers.
298
- - Ready-made authentication pages and UI components.
299
- - Application-specific authorization and role management.
300
- - Application-specific email templates and delivery workflows.
53
+ ## Security notes
301
54
 
302
- These capabilities can be built on top of the authentication primitives without coupling the core package to a specific application or framework.
55
+ Auth requires durable storage with the capabilities used by the configured managers. Production deployments should use HTTPS, secure HttpOnly cookies, shared persistent rate limiting, a maintained Argon2id password hasher, encrypted TOTP secrets, hashed recovery codes, and exact WebAuthn origin/RP ID configuration. The package does not provide OAuth, application authorization, or ready-made auth pages.
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@puzzle-ui/auth",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Framework-independent authentication core for Puzzle.",
5
+ "homepage": "https://puzzle-ui.com",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/senorprogramador/puzzle.git"
9
+ },
5
10
  "license": "MIT",
6
11
  "publishConfig": {
7
12
  "access": "public",
@@ -40,8 +45,8 @@
40
45
  "typescript": "^7.0.2"
41
46
  },
42
47
  "dependencies": {
43
- "@puzzle-ui/storage": "^0.5.0",
44
- "@puzzle-ui/session": "^0.5.0",
48
+ "@puzzle-ui/storage": "^0.5.1",
49
+ "@puzzle-ui/session": "^0.5.1",
45
50
  "@simplewebauthn/server": "^14.1.0"
46
51
  }
47
52
  }