@puzzle-ui/fullstack 0.5.0 → 0.6.0

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 +27 -520
  2. package/package.json +9 -4
package/README.md CHANGED
@@ -1,559 +1,66 @@
1
1
  # @puzzle-ui/fullstack
2
2
 
3
- First-party full-stack integrations for Puzzle.
3
+ [Documentation: https://puzzle-ui.com/docs/fullstack](https://puzzle-ui.com/docs/fullstack)
4
4
 
5
- The package provides browser-safe authentication functionality, server-side authentication handlers, reusable authentication screens and framework adapters.
5
+ `@puzzle-ui/fullstack` connects Puzzle Auth to browser clients, React UI, server routes, and framework adapters. Use it when an application needs a ready-to-use browser auth client or Request/Response handlers; configure auth policy, storage, and sessions with the lower-level packages.
6
6
 
7
- Authentication policies, persistence and security-sensitive operations remain in the framework-independent `@puzzle-ui/auth` and `@puzzle-ui/session` packages.
8
-
9
- ## Installation
7
+ ## Install
10
8
 
11
9
  ```bash
12
- bun add @puzzle-ui/fullstack @puzzle-ui/auth @puzzle-ui/session @puzzle-ui/ui
13
- ```
14
-
15
- Install React 19 and the appropriate framework peer dependencies in your application.
16
-
17
- ## Package architecture
18
-
19
- ```text
20
- @puzzle-ui/fullstack
21
- └── auth/
22
- ├── client/
23
- │ ├── index.ts
24
- │ ├── react.tsx
25
- │ ├── messages.ts
26
- │ ├── auth.css
27
- │ └── passkeys.ts
28
- ├── server/
29
- │ ├── index.ts
30
- │ ├── mfa.ts
31
- │ ├── lifecycle.ts
32
- │ └── session.ts
33
- ├── shared/
34
- │ └── index.ts
35
- ├── next/
36
- │ └── index.ts
37
- └── tanstack/
38
- └── index.ts
10
+ npm install @puzzle-ui/fullstack react react-dom
39
11
  ```
40
12
 
41
- ## Public exports
42
-
43
- | Import | Responsibility |
44
- | ------------------------------------ | ----------------------------------------------------------- |
45
- | `@puzzle-ui/fullstack/auth/client` | React provider, hooks, authentication UI and browser client |
46
- | `@puzzle-ui/fullstack/auth/server` | Server handlers, sessions and authentication lifecycle |
47
- | `@puzzle-ui/fullstack/auth/shared` | Serializable public contracts |
48
- | `@puzzle-ui/fullstack/auth/next` | Next.js App Router adapter |
49
- | `@puzzle-ui/fullstack/auth/tanstack` | TanStack Start adapter |
13
+ React and React DOM 19 are required peer dependencies. `next` (15 or 16) and `@tanstack/react-start` are optional peers for their respective adapters. On the server, configure the managers from `@puzzle-ui/auth` and `@puzzle-ui/session` with your storage backend. See the [Auth docs](https://puzzle-ui.com/docs/auth), [Storage docs](https://puzzle-ui.com/docs/storage), and [developer authentication guide](https://puzzle-ui.com/docs/developer/authentication).
50
14
 
51
15
  ## Client
52
16
 
53
- ### AuthProvider
54
-
55
- Wrap the application in `AuthProvider`.
17
+ Wrap the application in `AuthProvider`, then use the hooks or exported forms below it:
56
18
 
57
19
  ```tsx
58
- import { AuthProvider } from "@puzzle-ui/fullstack/auth/client";
20
+ import { AuthProvider, useAuth, useUser } from "@puzzle-ui/fullstack/auth/client";
59
21
 
60
22
  export const Providers = ({ children }: { children: React.ReactNode }) => (
61
- <AuthProvider
62
- options={{
63
- basePath: "/api/auth",
64
- }}
65
- locale="en"
66
- >
67
- {children}
68
- </AuthProvider>
23
+ <AuthProvider options={{ basePath: "/api/auth" }}>{children}</AuthProvider>
69
24
  );
70
- ```
71
-
72
- The provider creates and shares one authentication client.
73
-
74
- Authentication state is retrieved from the server. Session credentials are not stored in browser storage.
75
25
 
76
- ### Hooks
77
-
78
- ```tsx
79
- import { useAuth, useSession, useUser } from "@puzzle-ui/fullstack/auth/client";
80
-
81
- const Profile = () => {
26
+ export const Account = () => {
82
27
  const user = useUser();
83
- const session = useSession();
84
- const auth = useAuth();
85
-
86
- if (session.isLoading) return null;
87
-
88
- if (!user) return null;
89
-
90
- return (
91
- <div>
92
- <span>{user.email}</span>
93
- <button onClick={() => void auth.signOut()}>Sign out</button>
94
- </div>
95
- );
96
- };
97
- ```
98
-
99
- ### Authentication components
100
-
101
- Available components:
102
-
103
- - `SignInForm`
104
- - `SignUpForm`
105
- - `MfaChallengeForm`
106
- - `RequiredMfaEnrollment`
107
- - `PasswordResetRequestForm`
108
- - `PasswordResetForm`
109
- - `EmailVerificationForm`
110
- - `EmailVerificationNotice`
111
- - `ChangePasswordForm`
112
- - `MfaSettings`
113
- - `AuthScreen`
114
-
115
- All components support className customization.
116
-
117
- Redirects are opt-in through `redirectTo` and restricted to same-origin paths.
118
-
119
- ## Configurable authentication experience
120
-
121
- `@puzzle-ui/fullstack` provides an optional `AuthExperience` component for building branded sign-in and sign-up pages without having to implement the standard authentication flow yourself.
122
-
123
- The experience supports:
124
-
125
- - Custom branding, including a logo, title, description and footer.
126
- - Layout configuration, including content width and alignment.
127
- - Configurable redirects after successful authentication.
128
- - Custom registration fields with validation.
129
- - Custom submit callbacks for integrating application-specific registration logic.
130
- - Existing Puzzle authentication messages and form behavior.
131
-
132
- ### Basic usage
133
-
134
- ```tsx
135
- import { AuthExperience } from "@puzzle-ui/fullstack/auth/client";
136
-
137
- export const SignUpPage = () => (
138
- <AuthExperience
139
- mode="sign-up"
140
- config={{
141
- branding: {
142
- title: "Create your account",
143
- description: "Get started with your new account.",
144
- },
145
- layout: {
146
- width: "md",
147
- align: "center",
148
- },
149
- redirects: {
150
- afterSignUp: "/dashboard",
151
- },
152
- fields: [
153
- {
154
- name: "company",
155
- label: "Company",
156
- type: "text",
157
- required: true,
158
- },
159
- {
160
- name: "role",
161
- label: "Your role",
162
- type: "select",
163
- options: [
164
- { label: "Developer", value: "developer" },
165
- { label: "Designer", value: "designer" },
166
- { label: "Other", value: "other" },
167
- ],
168
- },
169
- ],
170
- }}
171
- />
172
- );
173
- ```
174
-
175
- ### Custom registration handling
176
-
177
- Custom fields are UI-only extensions. They are not automatically added to the authentication user model or persisted by the auth engine.
178
-
179
- The experience uses Puzzle UI form fields, text inputs, text areas, checkboxes,
180
- selects, buttons and typography. URL and number fields retain native inputs to
181
- preserve their input types and string values, which `TextInput` does not support.
182
-
183
- Use `onSubmit` to receive the standard registration input and custom field values. The callback can call `signUp` to execute the normal registration flow.
184
-
185
- ```tsx
186
- import { AuthExperience } from "@puzzle-ui/fullstack/auth/client";
187
-
188
- export const SignUpPage = () => (
189
- <AuthExperience
190
- mode="sign-up"
191
- config={{
192
- fields: [
193
- {
194
- name: "company",
195
- label: "Company",
196
- type: "text",
197
- required: true,
198
- },
199
- ],
200
- }}
201
- onSubmit={async ({ registration, fields, signUp }) => {
202
- await signUp(registration);
203
-
204
- // Handle application-specific values separately.
205
- await saveCompanyInformation({
206
- email: registration.email,
207
- company: fields.company,
208
- });
209
- }}
210
- />
211
- );
212
- ```
213
-
214
- ### Browser tests
215
-
216
- Run the Fullstack authentication experience browser tests with:
217
-
218
- ```bash
219
- bun run --filter @puzzle-ui/fullstack test:browser
220
- ```
221
-
222
- ### AuthScreen
223
-
224
- ```tsx
225
- import { AuthScreen } from "@puzzle-ui/fullstack/auth/client";
226
-
227
- export default function LoginPage() {
228
- return <AuthScreen mode="sign-in" redirectTo="/dashboard" />;
229
- }
230
- ```
231
-
232
- ### Registration
233
-
234
- ```tsx
235
- import { AuthScreen } from "@puzzle-ui/fullstack/auth/client";
236
-
237
- export default function RegisterPage() {
238
- return <AuthScreen mode="sign-up" />;
239
- }
240
- ```
241
-
242
- ### MFA management
243
-
244
- ```tsx
245
- import { MfaSettings } from "@puzzle-ui/fullstack/auth/client";
246
-
247
- export default function SecurityPage() {
248
- return <MfaSettings />;
249
- }
250
- ```
251
-
252
- ### Localization
253
-
254
- Puzzle Fullstack includes English messages as its default locale and fallback.
255
-
256
- Applications can define their own locale identifiers by extending the `AuthLocale` type. Every custom locale must provide a complete `AuthMessages` object. English messages are optional because the built-in English messages are used whenever no application-provided English messages are available.
257
-
258
- For example, an application can add Dutch and German:
259
-
260
- ```tsx
261
- import { AuthProvider } from "@puzzle-ui/fullstack/auth/client";
262
-
263
- import {
264
- getAuthLocale,
265
- resolveAuthMessages,
266
- type AuthLocale,
267
- type AuthLocaleMessages,
268
- } from "@puzzle-ui/fullstack/auth/client";
269
-
270
- type AppLocale = AuthLocale<"nl" | "de">;
271
-
272
- const appMessages: AuthLocaleMessages<"nl" | "de"> = {
273
- nl: {
274
- // Supply every AuthMessages field in Dutch.
275
- // For example:
276
- signIn: "Inloggen",
277
- signUp: "Account aanmaken",
278
- email: "E-mailadres",
279
- password: "Wachtwoord",
280
- name: "Naam",
281
- confirmPassword: "Bevestig wachtwoord",
282
- submit: "Doorgaan",
283
- signingIn: "Inloggen...",
284
- signingUp: "Account aanmaken...",
285
- forgotPassword: "Wachtwoord vergeten?",
286
- createAccount: "Account aanmaken",
287
- existingAccount: "Heb je al een account?",
288
- noAccount: "Nog geen account?",
289
- mfaTitle: "Verifieer je identiteit",
290
- mfaDescription: "Voer je authenticatiecode in om door te gaan.",
291
- verificationCode: "Authenticatiecode",
292
- recoveryCode: "Herstelcode",
293
- verify: "Verifiëren",
294
- verifying: "Verifiëren...",
295
- useRecoveryCode: "Gebruik een herstelcode",
296
- useAuthenticator: "Gebruik authenticator-app",
297
- usePasskey: "Doorgaan met passkey",
298
- passkeyUnavailable: "Passkeys zijn niet beschikbaar in deze browser.",
299
- loading: "Laden...",
300
- signOut: "Uitloggen",
301
- unexpectedError: "Er is iets misgegaan. Probeer het opnieuw.",
302
- invalidCredentials: "Het e-mailadres of wachtwoord is onjuist.",
303
- passwordMismatch: "De wachtwoorden komen niet overeen.",
304
- passwordRequired: "Een wachtwoord is verplicht.",
305
- requestPasswordReset: "Wachtwoord herstellen aanvragen",
306
- resetPassword: "Wachtwoord herstellen",
307
- newPassword: "Nieuw wachtwoord",
308
- sendResetLink: "Verstuur herstellink",
309
- passwordResetSent: "De herstellink is verstuurd",
310
- verificationSent: "Verificatiemail is verstuurd",
311
- verifyEmail: "E-mailadres verifiëren",
312
- changePassword: "Wachtwoord wijzigen",
313
- passwordChanged: "Wachtwoord gewijzigd",
314
- emailRequired: "Een e-mailadres is verplicht.",
315
- nameRequired: "Een naam is verplicht.",
316
- authenticated: "Je bent ingelogd.",
317
- mfaSettings: "Beveiligingsinstellingen",
318
- addTotp: "Authenticator toevoegen",
319
- addPasskey: "Passkey toevoegen",
320
- removeFactor: "Verwijderen",
321
- recoveryCodes: "Herstelcodes",
322
- saveRecoveryCodes: "Bewaar deze codes op een veilige plek.",
323
- currentPassword: "Huidig wachtwoord",
324
- totpSecret: "Authenticator-geheim",
325
- verifyTotp: "Authenticator verifiëren",
326
- cancel: "Annuleren",
327
- },
328
- // Optionally (partially)override messages for English:
329
- en: {
330
- signUp: "Create your account",
331
- }
332
- de: {
333
- // Supply every AuthMessages field in German.
334
- // The same complete set of fields as the Dutch example is required.
335
- },
336
- };
337
-
338
- // The locale type can be used throughout the application.
339
- const requestedLocale: string = "nl";
340
-
341
- const locale: AppLocale = getAuthLocale(requestedLocale, appMessages);
342
-
343
- // Resolve the complete message set before passing it to the provider.
344
- // Built-in English messages are used for any missing English fallback.
345
- const messages = resolveAuthMessages(locale, appMessages);
346
-
347
- export const Providers = ({ children }: { children: React.ReactNode }) => (
348
- <AuthProvider
349
- locale={locale}
350
- messages={messages}
351
- options={{
352
- basePath: "/api/auth",
353
- }}
354
- >
355
- {children}
356
- </AuthProvider>
357
- );
358
- ```
359
-
360
- The `AuthLocale` type accepts custom locale identifiers:
361
-
362
- ```ts
363
- type AppLocale = AuthLocale<"nl" | "de" | "es">;
364
- ```
28
+ const { signOut } = useAuth();
365
29
 
366
- The corresponding message map requires a complete message set for every declared custom locale:
367
-
368
- ```ts
369
- const messages: AuthLocaleMessages<"nl" | "de"> = {
370
- nl: dutchMessages,
371
- de: germanMessages,
372
-
373
- // Optional: English overrides.
374
- // Without this property, Puzzle's built-in English messages are used.
375
- // en: englishMessages,
30
+ return user ? <button onClick={() => void signOut()}>{user.email} · Sign out</button> : null;
376
31
  };
377
32
  ```
378
33
 
379
- The locale resolver only selects a custom locale when its messages are available:
380
-
381
- ```ts
382
- const locale = getAuthLocale("nl-NL", messages);
383
- // "en", because "nl-NL" is not a configured locale.
384
-
385
- const dutchLocale = getAuthLocale("nl", messages);
386
- // "nl"
387
-
388
- const fallbackLocale = getAuthLocale("fr", messages);
389
- // "en"
390
- ```
391
-
392
- Locale identifiers are exact matches. If an application wants to support regional identifiers such as `nl-NL` or `en-GB`, it should declare and provide those locales explicitly.
393
-
394
- Messages are resolved in this order:
34
+ The client provides auth state and actions, sign-in/sign-up and MFA components, `AuthExperience` for branded registration flows, locale helpers, and passkey browser helpers. Browser state is not an authorization boundary; protect server resources independently.
395
35
 
396
- 1. Built-in English messages.
397
- 2. Optional application-provided English messages.
398
- 3. Messages for the selected custom locale.
399
- 4. Optional per-message overrides.
36
+ ## Server and framework adapters
400
37
 
401
- This means a custom locale always has a complete message set, while English can remain entirely implicit.
402
-
403
- ## Server
404
-
405
- ### Creating the server
38
+ Create one server instance from configured auth and session managers, then register its routes:
406
39
 
407
40
  ```ts
408
- import { createAuthServer } from "@puzzle-ui/fullstack/auth/server";
409
-
410
- import {
411
- authManager,
412
- mfaManager,
413
- stepUpManager,
414
- sessions,
415
- cookie,
416
- csrf,
417
- } from "./auth-managers";
41
+ import { createAuthServer, createAuthRouteHandlers } from "@puzzle-ui/fullstack/auth/server";
418
42
 
419
43
  export const authServer = createAuthServer({
420
- auth: authManager,
421
- mfa: mfaManager,
422
- stepUp: stepUpManager,
44
+ auth,
45
+ mfa,
46
+ stepUp,
423
47
  sessions,
424
48
  cookie,
425
49
  csrf,
426
50
  resolveClientIp,
427
51
  });
428
- ```
429
-
430
- The supplied managers and providers must be initialized using their respective package contracts.
431
-
432
- ### Route handlers
433
-
434
- `createAuthRouteHandlers` returns a map of paths and HTTP methods to Web Request handlers.
435
-
436
- The default base path is `/api/auth`.
437
-
438
- A custom base path can be configured.
439
-
440
- ### Lifecycle integration
441
-
442
- The optional lifecycle integration provides:
443
52
 
444
- - Email verification requests.
445
- - Email verification.
446
- - Password reset requests.
447
- - Password reset completion.
448
- - Authenticated password changes.
449
-
450
- Password reset requests use a generic public response to prevent account enumeration.
451
-
452
- ### Session security
453
-
454
- - Session tokens remain in HttpOnly cookies.
455
- - State-changing requests require CSRF protection.
456
- - Authentication responses use no-store caching.
457
- - Password changes invalidate existing sessions.
458
- - MFA challenge tokens remain server-managed.
459
- - Client-facing responses contain only public user data.
460
-
461
- ## Next.js App Router
462
-
463
- ```ts
464
- // app/api/auth/[...auth]/route.ts
465
-
466
- import { auth } from "@/lib/auth";
467
-
468
- import { createNextAuthHandler } from "@puzzle-ui/fullstack/auth/next";
469
-
470
- const handlers = createNextAuthHandler({
471
- auth,
472
- });
473
-
474
- export const GET = handlers.GET;
475
- export const POST = handlers.POST;
476
- ```
477
-
478
- ## TanStack Start
479
-
480
- ```ts
481
- import { createTanStackAuthHandler } from "@puzzle-ui/fullstack/auth/tanstack";
482
-
483
- import { auth } from "./auth";
484
-
485
- export const authHandler = createTanStackAuthHandler({
486
- auth,
487
- });
488
- ```
489
-
490
- Register the handler using the application's TanStack server route configuration.
491
-
492
- ## Runtime support
493
-
494
- The package uses Web APIs for HTTP request and response handling.
495
-
496
- Supported targets:
497
-
498
- - Node.js
499
- - Cloudflare Workers
500
- - Bun development environments
501
- - Browser authentication interfaces
502
-
503
- Persistent sessions, tokens and authentication records must use durable storage.
504
-
505
- ## Build
506
-
507
- ```bash
508
- bun run --filter @puzzle-ui/fullstack build
53
+ export const authRoutes = createAuthRouteHandlers(authServer);
509
54
  ```
510
55
 
511
- ## Typecheck
512
-
513
- ```bash
514
- bun run --filter @puzzle-ui/fullstack typecheck
515
- ```
516
-
517
- ## Tests
518
-
519
- Tests run against the built package with Node's test runner. The test setup
520
- ignores CSS imports so client components can be rendered without a browser.
521
-
522
- ```bash
523
- bun run --filter @puzzle-ui/fullstack build
524
- bun run --filter @puzzle-ui/fullstack test
525
- ```
526
-
527
- The required TOTP and passkey enrollment end-to-end tests live in this package
528
- because they exercise the HTTP handlers together with the auth managers:
529
-
530
- ```bash
531
- node --test packages/fullstack/tests/auth-mfa.e2e.test.mjs
532
- ```
533
-
534
- ## Security boundaries
535
-
536
- The package maintains separate public entry points for:
537
-
538
- - Client functionality.
539
- - Server functionality.
540
- - Shared contracts.
541
- - Framework integrations.
542
-
543
- Client code must never import server implementations, database adapters, secrets or password hashing implementations.
544
-
545
- Server functionality must never be re-exported from browser-facing entry points.
546
-
547
- Framework adapters must delegate security policies and authentication operations to the underlying authentication packages.
548
-
549
- ## Scope
56
+ Use `@puzzle-ui/fullstack/auth/next` for Next.js `GET`/`POST` route handlers or `@puzzle-ui/fullstack/auth/tanstack` to create a TanStack Start Request dispatcher. Configure cookies, CSRF, storage and trusted proxy IP resolution in the server layer. Do not expose server managers to browser bundles.
550
57
 
551
- This package is designed to support additional full-stack Puzzle modules in the future.
58
+ ## Entry points
552
59
 
553
- Authentication is the first module. Other modules should follow the same principles:
60
+ - `@puzzle-ui/fullstack/auth/client` — browser client, React provider/hooks/components, auth experience, localization, and passkey helpers.
61
+ - `@puzzle-ui/fullstack/auth/server` — server handlers, route map, session and step-up helpers, lifecycle handlers, readiness checks.
62
+ - `@puzzle-ui/fullstack/auth/shared` — serializable public auth contracts (types only).
63
+ - `@puzzle-ui/fullstack/auth/next` — Next.js route-handler adapter.
64
+ - `@puzzle-ui/fullstack/auth/tanstack` — TanStack Start adapter.
554
65
 
555
- - Explicit client/server boundaries.
556
- - Framework-independent domain logic.
557
- - Serializable shared contracts.
558
- - Thin framework adapters.
559
- - Explicit public exports.
66
+ There is no root package export; import a documented subpath. See the [full documentation](https://puzzle-ui.com/docs/fullstack) for API details and production guidance.
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@puzzle-ui/fullstack",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "First-party Puzzle Auth client, server and framework integration entry points.",
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",
@@ -88,9 +93,9 @@
88
93
  "vitest": "latest"
89
94
  },
90
95
  "dependencies": {
91
- "@puzzle-ui/ui": "^0.5.0",
92
- "@puzzle-ui/auth": "^0.5.0",
93
- "@puzzle-ui/session": "^0.5.0",
96
+ "@puzzle-ui/ui": "^0.6.0",
97
+ "@puzzle-ui/auth": "^0.6.0",
98
+ "@puzzle-ui/session": "^0.6.0",
94
99
  "@simplewebauthn/browser": "^14.0.0"
95
100
  },
96
101
  "peerDependencies": {