@sylphx/sdk 0.8.0-rc.1 → 0.8.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.
@@ -1,567 +0,0 @@
1
- import { NextResponse, NextRequest } from 'next/server';
2
- import { AuthTokensResponse } from '@sylphx/contract';
3
-
4
- /**
5
- * Sylphx Unified Middleware — State of the Art
6
- *
7
- * ONE middleware function handles EVERYTHING:
8
- * - Auth routes (mounted automatically, zero manual API routes)
9
- * - Token refresh (automatic, every request)
10
- * - Route protection
11
- * - Cookie management
12
- *
13
- * This follows Auth0 v4 / Clerk / Supabase patterns where middleware
14
- * handles all auth concerns. Apps don't need to create any /api/auth/* routes.
15
- *
16
- * @example
17
- * ```typescript
18
- * // middleware.ts (or proxy.ts for Next.js 16)
19
- * import { createSylphxMiddleware } from '@sylphx/sdk/nextjs'
20
- *
21
- * export default createSylphxMiddleware({
22
- * publicRoutes: ['/', '/about', '/pricing'],
23
- * })
24
- *
25
- * export const config = {
26
- * matcher: ['/((?!_next|.*\\..*).*)', '/'],
27
- * }
28
- * ```
29
- *
30
- * That's it. No /api/auth/* routes needed.
31
- */
32
-
33
- interface SylphxMiddlewareConfig {
34
- /**
35
- * Routes that don't require authentication.
36
- * Supports exact paths and wildcards.
37
- *
38
- * @example ['/', '/about', '/pricing', '/blog/*']
39
- * @default ['/']
40
- */
41
- publicRoutes?: string[];
42
- /**
43
- * Routes that middleware should completely ignore.
44
- * Use for webhooks, static assets, or third-party integrations.
45
- *
46
- * @default []
47
- */
48
- ignoredRoutes?: string[];
49
- /**
50
- * URL to redirect unauthenticated users.
51
- * @default '/login'
52
- */
53
- signInUrl?: string;
54
- /**
55
- * URL to redirect after sign out.
56
- * @default '/'
57
- */
58
- afterSignOutUrl?: string;
59
- /**
60
- * URL to redirect after successful sign in.
61
- * @default '/dashboard'
62
- */
63
- afterSignInUrl?: string;
64
- /**
65
- * Auth routes prefix. Routes are mounted at:
66
- * - {prefix}/callback — OAuth callback handler
67
- * - {prefix}/signout — Sign out handler
68
- *
69
- * @default '/auth'
70
- */
71
- authPrefix?: string;
72
- /**
73
- * Enable debug logging.
74
- * @default false
75
- */
76
- debug?: boolean;
77
- /**
78
- * Secret key for authentication.
79
- * Override to use a programmatic key instead of SYLPHX_SECRET_KEY env var.
80
- *
81
- * Use case: Platform Console uses dynamically generated keys.
82
- *
83
- * @default process.env.SYLPHX_SECRET_KEY
84
- */
85
- secretKey?: string;
86
- /**
87
- * Platform URL for API calls.
88
- * Override for self-hosted or same-origin deployments.
89
- *
90
- * @default https://api.sylphx.com
91
- */
92
- platformUrl?: string;
93
- /**
94
- * Callback to add custom headers/logic to responses.
95
- * Called for every non-auth-route request after SDK processing.
96
- *
97
- * Use case: Add security headers, tracking cookies, etc.
98
- *
99
- * @example
100
- * ```typescript
101
- * onResponse: (response, request) => {
102
- * response.headers.set('X-Custom-Header', 'value')
103
- * }
104
- * ```
105
- */
106
- onResponse?: (response: NextResponse, request: NextRequest) => void | Promise<void>;
107
- }
108
- /**
109
- * Create Sylphx middleware — State of the Art
110
- *
111
- * ONE function handles everything:
112
- * - Auth routes ({authPrefix}/callback, {authPrefix}/signout)
113
- * - Token refresh (automatic, every request)
114
- * - Route protection
115
- * - Cookie management
116
- *
117
- * @example
118
- * ```typescript
119
- * // middleware.ts (or proxy.ts for Next.js 16)
120
- * import { createSylphxMiddleware } from '@sylphx/sdk/nextjs'
121
- *
122
- * export default createSylphxMiddleware({
123
- * publicRoutes: ['/', '/about', '/pricing'],
124
- * })
125
- *
126
- * export const config = {
127
- * matcher: ['/((?!_next|.*\\..*).*)', '/'],
128
- * }
129
- * ```
130
- */
131
- declare function createSylphxMiddleware(userConfig?: SylphxMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>;
132
- /**
133
- * Create recommended matcher config
134
- */
135
- declare function createMatcher(): {
136
- matcher: string[];
137
- };
138
- /**
139
- * Get cookie namespace from secret key (for advanced use)
140
- */
141
- declare function getNamespace(secretKey: string): string;
142
-
143
- /**
144
- * Auth Functions
145
- *
146
- * Pure functions for authentication - no hidden state.
147
- * Each function takes config as the first parameter.
148
- *
149
- * Uses REST API at /api/sdk/auth/* for all operations.
150
- *
151
- * Types are re-exported from `@sylphx/contract` (ADR-084). The contract is
152
- * the single source of truth for every wire shape — this module only adds
153
- * SDK-specific ergonomics (User brand swap, introspection result, invite
154
- * envelopes, org-token claims).
155
- */
156
-
157
- /**
158
- * Token response — contract's `AuthTokensResponse.user` (optional `AuthUser`)
159
- * is re-mapped to the SDK's broader `User` type so legacy callers keep the
160
- * familiar brand. `AuthUser` and `User` are structurally identical, but
161
- * the SDK surface has wider reach (cookies, middleware, React hooks) and
162
- * renaming is out of scope for ADR-084 cleanup.
163
- */
164
- type TokenResponse = Omit<AuthTokensResponse, 'user'> & {
165
- user: User;
166
- };
167
-
168
- /**
169
- * SDK-specific types — cross-layer helpers and server-first configuration.
170
- *
171
- * Wire-shape types (API request/response envelopes) live in
172
- * `@sylphx/contract` and are re-exported per namespace from their SDK
173
- * module (e.g. `Plan` / `Subscription` from `./billing`, `ConsentType`
174
- * from `./consent`, `TokenResponse` from `./auth`). React-hook wrapper
175
- * shapes live in `./react/types` (tRPC-like convenience shapes that
176
- * are not part of the platform wire).
177
- *
178
- * History: pre-ADR-084 this file mirrored every wire shape the SDK
179
- * exposed; those aliases now come directly from `@sylphx/contract`.
180
- */
181
-
182
- /** SDK cookie/token shape. Richer authenticated surfaces live in `./react/types` `UserProfile`. */
183
- interface User {
184
- id: string;
185
- email: string;
186
- name: string | null;
187
- image?: string | null;
188
- emailVerified?: boolean;
189
- role?: string;
190
- createdAt?: string;
191
- }
192
- interface UserCookieData {
193
- user: User;
194
- /** Epoch ms when the session expires (client-side expiry check). */
195
- expiresAt: number;
196
- }
197
-
198
- /**
199
- * Server-side Auth Helpers for Next.js
200
- *
201
- * Use in Server Components and API Routes.
202
- *
203
- * Architecture: Cookie-Centric Single Source of Truth
204
- * ===================================================
205
- *
206
- * All auth state lives in cookies. The auth() function reads
207
- * from HttpOnly cookies and refreshes if needed.
208
- *
209
- * Cookie Structure:
210
- * - __sylphx_{namespace}_session — HttpOnly JWT (5 min)
211
- * - __sylphx_{namespace}_refresh — HttpOnly refresh token (30 days)
212
- * - __sylphx_{namespace}_user — JS-readable user data (5 min)
213
- */
214
-
215
- /**
216
- * Configure the SDK for server-side usage
217
- *
218
- * NOTE: This is optional! The SDK auto-configures from environment variables:
219
- * - SYLPHX_SECRET_KEY (required)
220
- *
221
- * Use this only if you need to override the default configuration.
222
- *
223
- * @example
224
- * ```ts
225
- * // Optional: Override default configuration
226
- * import { configureServer } from '@sylphx/sdk/nextjs'
227
- *
228
- * configureServer({
229
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
230
- * })
231
- * ```
232
- */
233
- declare function configureServer(config: {
234
- secretKey: string;
235
- platformUrl?: string;
236
- }): void;
237
- /**
238
- * Auth state returned by auth()
239
- */
240
- interface AuthResult {
241
- userId: string | null;
242
- user: User | null;
243
- /** Session token (for internal use only - not exposed to client) */
244
- sessionToken: string | null;
245
- }
246
- /**
247
- * Get the current auth state (memoized per request)
248
- *
249
- * This is the primary way to check authentication in Server Components.
250
- * It reads from HttpOnly cookies and refreshes if needed.
251
- *
252
- * @example
253
- * ```ts
254
- * // In a Server Component
255
- * import { auth } from '@sylphx/platform-sdk/nextjs'
256
- *
257
- * export default async function Dashboard() {
258
- * const { userId, user } = await auth()
259
- *
260
- * if (!userId) {
261
- * redirect('/login')
262
- * }
263
- *
264
- * return <div>Hello, {user?.name}</div>
265
- * }
266
- * ```
267
- */
268
- declare const auth: () => Promise<AuthResult>;
269
- /**
270
- * Get the current user (null if not logged in)
271
- *
272
- * @example
273
- * ```ts
274
- * import { currentUser } from '@sylphx/platform-sdk/nextjs'
275
- *
276
- * export default async function Header() {
277
- * const user = await currentUser()
278
- * if (!user) return <SignIn />
279
- * return <span>Hello, {user.name}</span>
280
- * }
281
- * ```
282
- */
283
- declare function currentUser(): Promise<User | null>;
284
- /**
285
- * Get the current user ID (null if not logged in)
286
- */
287
- declare function currentUserId(): Promise<string | null>;
288
- /**
289
- * Handle OAuth callback - exchange code for tokens and set cookies
290
- *
291
- * NOTE: With createSylphxMiddleware(), this is handled automatically.
292
- * You don't need to create manual /api/auth/* routes.
293
- *
294
- * This function is exported for advanced use cases where you need
295
- * custom callback handling outside of the middleware flow.
296
- *
297
- * @example
298
- * ```ts
299
- * // Using middleware (recommended - zero manual routes)
300
- * import { createSylphxMiddleware } from '@sylphx/sdk/nextjs'
301
- * export default createSylphxMiddleware({ publicRoutes: ['/', '/about'] })
302
- *
303
- * // Middleware automatically handles /auth/callback
304
- * ```
305
- */
306
- declare function handleCallback(code: string): Promise<User>;
307
- /**
308
- * Sign out - clear cookies and optionally revoke token
309
- *
310
- * NOTE: With createSylphxMiddleware(), signout is handled automatically
311
- * at /auth/signout. No manual route needed.
312
- *
313
- * This function is exported for advanced use cases.
314
- *
315
- * @example
316
- * ```ts
317
- * // Using middleware (recommended)
318
- * // Navigate users to /auth/signout - middleware handles everything
319
- * <a href="/auth/signout">Sign Out</a>
320
- * ```
321
- */
322
- declare function signOut(): Promise<void>;
323
- /**
324
- * Sync tokens to cookies (server action)
325
- *
326
- * NOTE: With createSylphxMiddleware(), OAuth callbacks are handled
327
- * automatically at /auth/callback. This function is rarely needed.
328
- *
329
- * Use only for edge cases like custom OAuth providers not going
330
- * through the standard flow.
331
- */
332
- declare function syncAuthToCookies(tokens: TokenResponse): Promise<void>;
333
- /**
334
- * Get authorization URL for OAuth redirect
335
- */
336
- declare function getAuthorizationUrl(options?: {
337
- redirectUri?: string;
338
- mode?: 'login' | 'signup';
339
- state?: string;
340
- appId?: string;
341
- }): string;
342
- /**
343
- * Get the current session token for API calls
344
- *
345
- * This is for apps that need to call third-party APIs with the session token.
346
- * For same-origin API calls, cookies are sent automatically.
347
- *
348
- * @example
349
- * ```ts
350
- * // In an API route that needs to call external APIs
351
- * import { getSessionToken } from '@sylphx/platform-sdk/nextjs'
352
- *
353
- * export async function GET() {
354
- * const token = await getSessionToken()
355
- * if (!token) {
356
- * return new Response('Unauthorized', { status: 401 })
357
- * }
358
- *
359
- * // Call third-party API
360
- * const response = await fetch('https://api.example.com/data', {
361
- * headers: { Authorization: `Bearer ${token}` }
362
- * })
363
- * // ...
364
- * }
365
- * ```
366
- */
367
- declare function getSessionToken(): Promise<string | null>;
368
-
369
- /**
370
- * Token expiry buffer in milliseconds (30 seconds)
371
- *
372
- * Refresh tokens this many milliseconds BEFORE they expire
373
- * to account for network latency and clock skew.
374
- */
375
- declare const TOKEN_EXPIRY_BUFFER_MS = 30000;
376
- /** Session token lifetime in milliseconds (for React Query staleTime) */
377
- declare const SESSION_TOKEN_LIFETIME_MS: number;
378
-
379
- /**
380
- * Platform ID utilities for the Sylphx SDK
381
- *
382
- * Converts between raw UUIDs (used internally / in JWT sub claims) and
383
- * prefixed TypeID strings (used in all API responses and JWT pid claims).
384
- *
385
- * Uses TypeID spec v0.3.0 (Crockford base32, case-insensitive).
386
- * Also accepts legacy base58 format for backward compatibility.
387
- *
388
- * No external dependencies — pure TypeScript implementation of Crockford base32.
389
- *
390
- * @example
391
- * ```ts
392
- * import { encodeUserId, decodeUserId } from '@sylphx/sdk/nextjs'
393
- *
394
- * const prefixed = encodeUserId('018f4a3b-1c2d-7000-9abc-def012345678')
395
- * // => 'user_01h2xcejqtf2nbrexx3vqjhp41'
396
- *
397
- * const uuid = decodeUserId('user_01h2xcejqtf2nbrexx3vqjhp41')
398
- * // => '018f4a3b-1c2d-7000-9abc-def012345678'
399
- * ```
400
- */
401
- /**
402
- * Encode a raw UUID as a prefixed TypeID user ID.
403
- *
404
- * @param uuid - Raw UUID string (with or without dashes)
405
- * @returns Prefixed TypeID: `user_<crockford_base32>`
406
- */
407
- declare function encodeUserId(uuid: string): string;
408
- /**
409
- * Decode a prefixed user ID back to a raw UUID.
410
- * Accepts both TypeID (current) and base58 (legacy) formats.
411
- *
412
- * @param prefixedId - Prefixed user ID: `user_<encoded>`
413
- * @returns Raw UUID string, or null if invalid
414
- */
415
- declare function decodeUserId(prefixedId: string): string | null;
416
-
417
- /**
418
- * Cookie Management for Next.js — Single Source of Truth
419
- *
420
- * Architecture: Cookie-Centric Auth (Clerk Pattern)
421
- * ================================================
422
- *
423
- * ALL auth state lives in cookies. Zero localStorage for auth.
424
- *
425
- * Cookie Structure:
426
- * - __sylphx_{namespace}_session — HttpOnly JWT, 5 min (access token)
427
- * - __sylphx_{namespace}_refresh — HttpOnly, 30 days (refresh token)
428
- * - __sylphx_{namespace}_user — JS-readable, 5 min (user data for client hydration)
429
- *
430
- * Benefits:
431
- * 1. Single Source of Truth — no server/client state divergence
432
- * 2. XSS-safe — tokens never accessible to JavaScript
433
- * 3. Cross-tab sync — cookies shared across tabs automatically
434
- * 4. SSR works — auth() in Server Components reads cookies directly
435
- *
436
- * Security:
437
- * - Short token lifetime (5 min) like Clerk
438
- * - Server-side refresh in middleware
439
- * - SameSite=Lax for CSRF protection
440
- */
441
-
442
- /**
443
- * Get cookie names for a given namespace
444
- *
445
- * Namespace is derived from the secret key environment (dev/stg/prod).
446
- * This prevents cookies from different environments colliding.
447
- *
448
- * @example
449
- * getCookieNames('sylphx_prod')
450
- * // Returns:
451
- * // {
452
- * // SESSION: '__sylphx_prod_session',
453
- * // REFRESH: '__sylphx_prod_refresh',
454
- * // USER: '__sylphx_prod_user',
455
- * // }
456
- */
457
- declare function getCookieNames(namespace: string): {
458
- /** HttpOnly JWT access token (5 min) */
459
- SESSION: string;
460
- /** HttpOnly refresh token (30 days) */
461
- REFRESH: string;
462
- /** JS-readable user data for client hydration (5 min) */
463
- USER: string;
464
- };
465
- /**
466
- * Session token lifetime (5 minutes like Clerk)
467
- */
468
- declare const SESSION_TOKEN_LIFETIME: number;
469
- /**
470
- * Refresh token lifetime (30 days)
471
- */
472
- declare const REFRESH_TOKEN_LIFETIME: number;
473
- /**
474
- * Cookie options for HttpOnly tokens (session, refresh)
475
- *
476
- * Security features:
477
- * - httpOnly: true — Not accessible via JavaScript (XSS protection)
478
- * - secure: true in production — Only sent over HTTPS
479
- * - sameSite: 'lax' — CSRF protection while allowing navigation
480
- */
481
- declare const SECURE_COOKIE_OPTIONS: {
482
- httpOnly: boolean;
483
- secure: boolean;
484
- sameSite: "lax";
485
- path: string;
486
- };
487
- /**
488
- * Cookie options for JS-readable user cookie
489
- *
490
- * This cookie contains only user info (no tokens) and enables:
491
- * - Client-side hydration without loading states
492
- * - Cross-tab sync via cookie visibility
493
- */
494
- declare const USER_COOKIE_OPTIONS: {
495
- httpOnly: boolean;
496
- secure: boolean;
497
- sameSite: "lax";
498
- path: string;
499
- };
500
- /**
501
- * Auth cookies data returned by getAuthCookies
502
- */
503
- interface AuthCookiesData {
504
- /** Access token from SESSION cookie (HttpOnly) */
505
- sessionToken: string | null;
506
- /** Refresh token from REFRESH cookie (HttpOnly) */
507
- refreshToken: string | null;
508
- /** User data from USER cookie (JS-readable) */
509
- user: User | null;
510
- /** Expiry timestamp from USER cookie */
511
- expiresAt: number | null;
512
- }
513
- /**
514
- * Get auth cookies from the request
515
- *
516
- * Used by auth() to read current auth state.
517
- */
518
- declare function getAuthCookies(namespace: string): Promise<AuthCookiesData>;
519
- /**
520
- * Set auth cookies from token response
521
- *
522
- * Sets all three cookies:
523
- * - SESSION: HttpOnly access token (5 min)
524
- * - REFRESH: HttpOnly refresh token (30 days)
525
- * - USER: JS-readable user data (5 min)
526
- *
527
- * @param namespace - Cookie namespace (e.g., 'sylphx_prod')
528
- * @param response - Token response from auth endpoint
529
- * @param options - Optional: custom expiresIn override
530
- */
531
- declare function setAuthCookies(namespace: string, response: TokenResponse, options?: {
532
- sessionLifetime?: number;
533
- }): Promise<void>;
534
- /**
535
- * Clear all auth cookies
536
- *
537
- * Call on sign out to remove all auth state.
538
- */
539
- declare function clearAuthCookies(namespace: string): Promise<void>;
540
- /**
541
- * Check if session is expired
542
- *
543
- * Uses a 30 second buffer to account for network latency.
544
- */
545
- declare function isSessionExpired(namespace: string): Promise<boolean>;
546
- /**
547
- * Check if we have a refresh token (can potentially refresh)
548
- */
549
- declare function hasRefreshToken(namespace: string): Promise<boolean>;
550
-
551
- /**
552
- * Set auth cookies on a NextResponse (for middleware use)
553
- *
554
- * Unlike setAuthCookies() which uses next/headers, this works with NextResponse.
555
- * Use this in middleware where you need to modify cookies on the response.
556
- */
557
- declare function setAuthCookiesMiddleware(response: NextResponse, namespace: string, tokens: TokenResponse): void;
558
- /**
559
- * Clear auth cookies on a NextResponse (for middleware use)
560
- */
561
- declare function clearAuthCookiesMiddleware(response: NextResponse, namespace: string): void;
562
- /**
563
- * Parse user cookie value (for client-side use)
564
- */
565
- declare function parseUserCookie(value: string): UserCookieData | null;
566
-
567
- export { type AuthCookiesData, type AuthResult, REFRESH_TOKEN_LIFETIME, SECURE_COOKIE_OPTIONS, SESSION_TOKEN_LIFETIME, SESSION_TOKEN_LIFETIME_MS, type SylphxMiddlewareConfig, TOKEN_EXPIRY_BUFFER_MS, USER_COOKIE_OPTIONS, type UserCookieData, auth, clearAuthCookies, clearAuthCookiesMiddleware, configureServer, createMatcher, createSylphxMiddleware, currentUser, currentUserId, decodeUserId, encodeUserId, getAuthCookies, getAuthorizationUrl, getCookieNames, getNamespace, getSessionToken, handleCallback, hasRefreshToken, isSessionExpired, parseUserCookie, setAuthCookies, setAuthCookiesMiddleware, signOut, syncAuthToCookies };