azirid-react 0.14.1 → 0.14.3

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 CHANGED
@@ -1415,7 +1415,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
1415
1415
 
1416
1416
  ### Middleware — Route Protection & JWT Validation
1417
1417
 
1418
- The middleware validates `__session` JWT cookies using JWKS (RS256) and protects routes.
1418
+ The middleware validates `__session` JWT cookies using JWKS (RS256) and protects routes server-side — before any HTML reaches the browser. No spinners, no flashes, no client-side redirect logic needed.
1419
1419
 
1420
1420
  #### Next.js 16+ (`proxy.ts`)
1421
1421
 
@@ -1424,9 +1424,25 @@ The middleware validates `__session` JWT cookies using JWKS (RS256) and protects
1424
1424
  import { createAziridProxy } from 'azirid-react/next/proxy'
1425
1425
 
1426
1426
  export const proxy = createAziridProxy({
1427
- protectedRoutes: ['/dashboard', '/settings'],
1427
+ // Routes that REQUIRE authentication.
1428
+ // Uses startsWith — '/dashboard' protects '/dashboard', '/dashboard/settings', etc.
1429
+ protectedRoutes: ['/dashboard', '/admin', '/settings'],
1430
+
1431
+ // Where to redirect unauthenticated users (default: '/login').
1432
+ // The original URL is preserved as ?redirect= so you can send them back after login.
1428
1433
  loginUrl: '/login',
1429
- publicRoutes: ['/login', '/signup', '/forgot-password'],
1434
+
1435
+ // Routes that are ALWAYS public, even if they match a protectedRoute.
1436
+ // Default: ['/login', '/signup', '/auth/handoff']
1437
+ publicRoutes: [
1438
+ '/login',
1439
+ '/register',
1440
+ '/forgot-password',
1441
+ '/reset-password',
1442
+ '/pricing',
1443
+ '/legal/terms',
1444
+ '/legal/privacy',
1445
+ ],
1430
1446
  })
1431
1447
 
1432
1448
  export const config = {
@@ -1434,13 +1450,25 @@ export const config = {
1434
1450
  }
1435
1451
  ```
1436
1452
 
1437
- | Option | Type | Default | Description |
1438
- | ------------------ | ---------- | ------------- | ------------------------------------------------ |
1439
- | `cookieName` | `string` | `"__session"` | Cookie carrying the access token JWT |
1440
- | `protectedRoutes` | `string[]` | — | Routes that require authentication |
1441
- | `loginUrl` | `string` | `"/login"` | Redirect target for unauthenticated users |
1442
- | `publicRoutes` | `string[]` | login/signup | Always-accessible routes |
1443
- | `jwksUrl` | `string` | auto | Override JWKS endpoint URL |
1453
+ #### How it works
1454
+
1455
+ 1. User visits `/dashboard/settings` without a valid token
1456
+ 2. Middleware checks: is `/dashboard/settings` protected? Yes (`startsWith('/dashboard')`)
1457
+ 3. Middleware checks: is it public? No
1458
+ 4. Middleware redirects to `/login?redirect=/dashboard/settings`
1459
+ 5. After login, you can read `?redirect=` and send them back
1460
+
1461
+ Routes not listed in `protectedRoutes` are accessible to everyone (e.g. `/`, `/pricing`, `/about`).
1462
+
1463
+ #### Options
1464
+
1465
+ | Option | Type | Default | Description |
1466
+ | ------------------ | ---------- | -------------------------------- | ------------------------------------------------------------- |
1467
+ | `protectedRoutes` | `string[]` | — | Routes that require auth (matched with `startsWith`) |
1468
+ | `loginUrl` | `string` | `"/login"` | Redirect target for unauthenticated users |
1469
+ | `publicRoutes` | `string[]` | `["/login", "/signup", "/auth/handoff"]` | Always-accessible routes, even if inside a protected path |
1470
+ | `cookieName` | `string` | `"__session"` | Cookie carrying the access token JWT |
1471
+ | `jwksUrl` | `string` | auto | Override JWKS endpoint URL |
1444
1472
 
1445
1473
  ---
1446
1474
 
package/dist/index.cjs CHANGED
@@ -4853,7 +4853,7 @@ function usePasswordToggle() {
4853
4853
  }
4854
4854
 
4855
4855
  // src/index.ts
4856
- var SDK_VERSION = "0.14.1";
4856
+ var SDK_VERSION = "0.14.3";
4857
4857
 
4858
4858
  exports.AUTH_BASE_PATH = AUTH_BASE_PATH;
4859
4859
  exports.AuthForm = AuthForm;
package/dist/index.js CHANGED
@@ -4851,7 +4851,7 @@ function usePasswordToggle() {
4851
4851
  }
4852
4852
 
4853
4853
  // src/index.ts
4854
- var SDK_VERSION = "0.14.1";
4854
+ var SDK_VERSION = "0.14.3";
4855
4855
 
4856
4856
  export { AUTH_BASE_PATH, AuthForm, AziridProvider, CheckoutButton, ForgotPasswordForm, HandoffCallback, InvoiceList, LoginForm, PATHS, PayButton, PayphoneCallback, PayphoneWidgetRenderer, PricingTable, ReferralCard, ReferralStats, ResetPasswordForm, SDK_VERSION, SignupForm, SubscriptionBadge, buildPaths, changePasswordSchema, cn, createAccessClient, createForgotPasswordSchema, createLoginSchema, createMutationHook, createResetPasswordConfirmSchema, createSignupSchema, en, es, forgotPasswordSchema, isAuthError, loginSchema, magicLinkRequestSchema, magicLinkVerifySchema, passkeyRegisterStartSchema, removeStyles, resetPasswordConfirmSchema, resolveMessages, signupSchema, socialLoginSchema, useAccessClient, useAzirid, useBootstrap, useBranches, useBranding, useChangePassword, useCheckout, useFormState, useInvoices, useLogin, useLogout, useMagicLink, useMessages, usePasskeys, usePasswordReset, usePasswordToggle, usePayButton, usePaymentMethods, usePaymentProviders, usePayphoneCheckout, usePayphoneConfirm, usePlans, useReferral, useReferralStats, useRefresh, useSession, useSignup, useSocialLogin, useSubmitTransferProof, useSubscription, useSwitchTenant, useTenantMembers, useTenants, useTransferPayment, useTransferProofs, useUploadTransferProof };
4857
4857
  //# sourceMappingURL=index.js.map
@@ -27,7 +27,10 @@ function createRequestInterceptor(options) {
27
27
  const { payload } = await jose.jwtVerify(token, keySet);
28
28
  response.headers.set("x-azirid-token", token);
29
29
  if (payload.sub) response.headers.set("x-azirid-user-id", payload.sub);
30
- } catch {
30
+ } catch (err) {
31
+ if (typeof console !== "undefined") {
32
+ console.warn("[azirid-proxy] JWT validation failed:", err instanceof Error ? err.message : err);
33
+ }
31
34
  response.headers.set("Set-Cookie", `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
32
35
  }
33
36
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next-proxy.ts"],"names":["createRemoteJWKSet","NextResponse","jwtVerify"],"mappings":";;;;;;AA+CA,IAAI,IAAA,GAAqD,IAAA;AAEzD,SAAS,QAAQ,OAAA,EAAyD;AACxE,EAAA,IAAI,MAAM,OAAO,IAAA;AACjB,EAAA,MAAM,MACJ,OAAA,IACA,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,kBAAkB,wBAAwB,CAAA,yBAAA,CAAA;AAC3D,EAAA,IAAA,GAAOA,uBAAA,CAAmB,IAAI,GAAA,CAAI,GAAG,CAAC,CAAA;AACtC,EAAA,OAAO,IAAA;AACT;AAIA,SAAS,yBAAyB,OAAA,EAA8B;AAC9D,EAAA,MAAM,UAAA,GAAa,SAAS,UAAA,IAAc,WAAA;AAC1C,EAAA,MAAM,QAAA,GAAW,SAAS,QAAA,IAAY,QAAA;AACtC,EAAA,MAAM,aAAA,GAAgB,CAAC,QAAA,EAAU,SAAA,EAAW,eAAe,CAAA;AAC3D,EAAA,MAAM,YAAA,GAAe,OAAA,EAAS,YAAA,GAC1B,CAAC,mBAAG,IAAI,GAAA,CAAI,CAAC,GAAG,OAAA,CAAQ,YAAA,EAAc,eAAe,CAAC,CAAC,CAAA,GACvD,aAAA;AACJ,EAAA,MAAM,kBAAkB,OAAA,EAAS,eAAA;AAEjC,EAAA,OAAO,eAAe,QAAQ,OAAA,EAAsB;AAClD,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,OAAA,CAAQ,OAAA;AAC7B,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA,EAAG,KAAA;AAE/C,IAAA,MAAM,QAAA,GAAWC,oBAAa,IAAA,EAAK;AAEnC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,EAAS,OAAO,CAAA;AACvC,QAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAMC,cAAA,CAAU,OAAO,MAAM,CAAA;AAEjD,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAA,EAAkB,KAAK,CAAA;AAC5C,QAAA,IAAI,QAAQ,GAAA,EAAK,QAAA,CAAS,QAAQ,GAAA,CAAI,kBAAA,EAAoB,QAAQ,GAAG,CAAA;AAAA,MACvE,CAAA,CAAA,MAAQ;AAEN,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,YAAA,EAAc,CAAA,EAAG,UAAU,CAAA,kCAAA,CAAoC,CAAA;AAAA,MACtF;AAAA,IACF;AAGA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA;AAC3D,MAAA,MAAM,QAAA,GAAW,aAAa,IAAA,CAAK,CAAC,MAAM,QAAA,CAAS,UAAA,CAAW,CAAC,CAAC,CAAA;AAChE,MAAA,MAAM,WAAA,GAAc,gBAAgB,IAAA,CAAK,CAAC,MAAM,QAAA,CAAS,UAAA,CAAW,CAAC,CAAC,CAAA;AAEtE,MAAA,IAAI,WAAA,IAAe,CAAC,QAAA,IAAY,CAAC,aAAA,EAAe;AAC9C,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAM;AAClC,QAAA,GAAA,CAAI,QAAA,GAAW,QAAA;AACf,QAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AACzC,QAAA,OAAOD,mBAAA,CAAa,SAAS,GAAG,CAAA;AAAA,MAClC;AAAA,IACF;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;AAyBO,SAAS,kBAAkB,OAAA,EAA8B;AAC9D,EAAA,OAAO,yBAAyB,OAAO,CAAA;AACzC;AAGO,IAAM,cAAc,iBAAA","file":"next-proxy.cjs","sourcesContent":["/**\n * Next.js middleware for Azirid Access.\n *\n * Validates the `__session` JWT cookie using JWKS and provides\n * route protection and token forwarding for server components.\n *\n * @example Next.js 16+ (proxy.ts)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Next.js 16+ with custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @packageDocumentation\n */\n\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { createRemoteJWKSet, jwtVerify } from 'jose'\n\n// ─── Options ────────────────────────────────────────────────\n\nexport interface AziridProxyOptions {\n /** Cookie name that carries the access token JWT (default: \"__session\") */\n cookieName?: string\n /** Routes that require authentication (matched via startsWith) */\n protectedRoutes?: string[]\n /** Route to redirect to when not authenticated (default: \"/login\") */\n loginUrl?: string\n /** Routes that are always public (default: [\"/login\", \"/signup\"]) */\n publicRoutes?: string[]\n /** JWKS endpoint URL. Default: `${AZIRID_API_URL}/v1/.well-known/jwks.json` */\n jwksUrl?: string\n}\n\n// ─── JWKS (lazy-initialized, cached by jose internally) ─────\n\nlet jwks: ReturnType<typeof createRemoteJWKSet> | null = null\n\nfunction getJwks(jwksUrl?: string): ReturnType<typeof createRemoteJWKSet> {\n if (jwks) return jwks\n const url =\n jwksUrl ??\n `${process.env.AZIRID_API_URL ?? 'https://api.azirid.com'}/v1/.well-known/jwks.json`\n jwks = createRemoteJWKSet(new URL(url))\n return jwks\n}\n\n// ─── Middleware logic ───────────────────────────────────────\n\nfunction createRequestInterceptor(options?: AziridProxyOptions) {\n const cookieName = options?.cookieName ?? '__session'\n const loginUrl = options?.loginUrl ?? '/login'\n const defaultPublic = ['/login', '/signup', '/auth/handoff']\n const publicRoutes = options?.publicRoutes\n ? [...new Set([...options.publicRoutes, '/auth/handoff'])]\n : defaultPublic\n const protectedRoutes = options?.protectedRoutes\n\n return async function handler(request: NextRequest) {\n const { pathname } = request.nextUrl\n const token = request.cookies.get(cookieName)?.value\n\n const response = NextResponse.next()\n\n if (token) {\n try {\n const keySet = getJwks(options?.jwksUrl)\n const { payload } = await jwtVerify(token, keySet)\n // Valid token — expose for server components via headers\n response.headers.set('x-azirid-token', token)\n if (payload.sub) response.headers.set('x-azirid-user-id', payload.sub)\n } catch {\n // Invalid or expired JWT — clear corrupted cookie\n response.headers.set('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`)\n }\n }\n\n // Route protection (only when protectedRoutes is configured)\n if (protectedRoutes) {\n const hasValidToken = response.headers.has('x-azirid-token')\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r))\n const isProtected = protectedRoutes.some((r) => pathname.startsWith(r))\n\n if (isProtected && !isPublic && !hasValidToken) {\n const url = request.nextUrl.clone()\n url.pathname = loginUrl\n url.searchParams.set('redirect', pathname)\n return NextResponse.redirect(url)\n }\n }\n\n return response\n }\n}\n\n// ─── Proxy (Next.js 16+) ────────────────────────────────────\n\n/**\n * Create a customized Azirid middleware for Next.js 16+.\n *\n * @example Default (one line)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n */\nexport function createAziridProxy(options?: AziridProxyOptions) {\n return createRequestInterceptor(options)\n}\n\n/** Default proxy export for Next.js 16+ (no route protection, only validates + forwards token) */\nexport const aziridProxy = createAziridProxy()\n"]}
1
+ {"version":3,"sources":["../src/next-proxy.ts"],"names":["createRemoteJWKSet","NextResponse","jwtVerify"],"mappings":"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","file":"next-proxy.cjs","sourcesContent":["/**\n * Next.js middleware for Azirid Access.\n *\n * Validates the `__session` JWT cookie using JWKS and provides\n * route protection and token forwarding for server components.\n *\n * @example Next.js 16+ (proxy.ts)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Next.js 16+ with custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @packageDocumentation\n */\n\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { createRemoteJWKSet, jwtVerify } from 'jose'\n\n// ─── Options ────────────────────────────────────────────────\n\nexport interface AziridProxyOptions {\n /** Cookie name that carries the access token JWT (default: \"__session\") */\n cookieName?: string\n /** Routes that require authentication (matched via startsWith) */\n protectedRoutes?: string[]\n /** Route to redirect to when not authenticated (default: \"/login\") */\n loginUrl?: string\n /** Routes that are always public (default: [\"/login\", \"/signup\"]) */\n publicRoutes?: string[]\n /** JWKS endpoint URL. Default: `${AZIRID_API_URL}/v1/.well-known/jwks.json` */\n jwksUrl?: string\n}\n\n// ─── JWKS (lazy-initialized, cached by jose internally) ─────\n\nlet jwks: ReturnType<typeof createRemoteJWKSet> | null = null\n\nfunction getJwks(jwksUrl?: string): ReturnType<typeof createRemoteJWKSet> {\n if (jwks) return jwks\n const url =\n jwksUrl ??\n `${process.env.AZIRID_API_URL ?? 'https://api.azirid.com'}/v1/.well-known/jwks.json`\n jwks = createRemoteJWKSet(new URL(url))\n return jwks\n}\n\n// ─── Middleware logic ───────────────────────────────────────\n\nfunction createRequestInterceptor(options?: AziridProxyOptions) {\n const cookieName = options?.cookieName ?? '__session'\n const loginUrl = options?.loginUrl ?? '/login'\n const defaultPublic = ['/login', '/signup', '/auth/handoff']\n const publicRoutes = options?.publicRoutes\n ? [...new Set([...options.publicRoutes, '/auth/handoff'])]\n : defaultPublic\n const protectedRoutes = options?.protectedRoutes\n\n return async function handler(request: NextRequest) {\n const { pathname } = request.nextUrl\n const token = request.cookies.get(cookieName)?.value\n\n const response = NextResponse.next()\n\n if (token) {\n try {\n const keySet = getJwks(options?.jwksUrl)\n const { payload } = await jwtVerify(token, keySet)\n // Valid token — expose for server components via headers\n response.headers.set('x-azirid-token', token)\n if (payload.sub) response.headers.set('x-azirid-user-id', payload.sub)\n } catch (err) {\n // Invalid or expired JWT — clear corrupted cookie\n if (typeof console !== 'undefined') {\n console.warn('[azirid-proxy] JWT validation failed:', err instanceof Error ? err.message : err)\n }\n response.headers.set('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`)\n }\n }\n\n // Route protection (only when protectedRoutes is configured)\n if (protectedRoutes) {\n const hasValidToken = response.headers.has('x-azirid-token')\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r))\n const isProtected = protectedRoutes.some((r) => pathname.startsWith(r))\n\n if (isProtected && !isPublic && !hasValidToken) {\n const url = request.nextUrl.clone()\n url.pathname = loginUrl\n url.searchParams.set('redirect', pathname)\n return NextResponse.redirect(url)\n }\n }\n\n return response\n }\n}\n\n// ─── Proxy (Next.js 16+) ────────────────────────────────────\n\n/**\n * Create a customized Azirid middleware for Next.js 16+.\n *\n * @example Default (one line)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n */\nexport function createAziridProxy(options?: AziridProxyOptions) {\n return createRequestInterceptor(options)\n}\n\n/** Default proxy export for Next.js 16+ (no route protection, only validates + forwards token) */\nexport const aziridProxy = createAziridProxy()\n"]}
@@ -25,7 +25,10 @@ function createRequestInterceptor(options) {
25
25
  const { payload } = await jwtVerify(token, keySet);
26
26
  response.headers.set("x-azirid-token", token);
27
27
  if (payload.sub) response.headers.set("x-azirid-user-id", payload.sub);
28
- } catch {
28
+ } catch (err) {
29
+ if (typeof console !== "undefined") {
30
+ console.warn("[azirid-proxy] JWT validation failed:", err instanceof Error ? err.message : err);
31
+ }
29
32
  response.headers.set("Set-Cookie", `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
30
33
  }
31
34
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next-proxy.ts"],"names":[],"mappings":";;;;AA+CA,IAAI,IAAA,GAAqD,IAAA;AAEzD,SAAS,QAAQ,OAAA,EAAyD;AACxE,EAAA,IAAI,MAAM,OAAO,IAAA;AACjB,EAAA,MAAM,MACJ,OAAA,IACA,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,kBAAkB,wBAAwB,CAAA,yBAAA,CAAA;AAC3D,EAAA,IAAA,GAAO,kBAAA,CAAmB,IAAI,GAAA,CAAI,GAAG,CAAC,CAAA;AACtC,EAAA,OAAO,IAAA;AACT;AAIA,SAAS,yBAAyB,OAAA,EAA8B;AAC9D,EAAA,MAAM,UAAA,GAAa,SAAS,UAAA,IAAc,WAAA;AAC1C,EAAA,MAAM,QAAA,GAAW,SAAS,QAAA,IAAY,QAAA;AACtC,EAAA,MAAM,aAAA,GAAgB,CAAC,QAAA,EAAU,SAAA,EAAW,eAAe,CAAA;AAC3D,EAAA,MAAM,YAAA,GAAe,OAAA,EAAS,YAAA,GAC1B,CAAC,mBAAG,IAAI,GAAA,CAAI,CAAC,GAAG,OAAA,CAAQ,YAAA,EAAc,eAAe,CAAC,CAAC,CAAA,GACvD,aAAA;AACJ,EAAA,MAAM,kBAAkB,OAAA,EAAS,eAAA;AAEjC,EAAA,OAAO,eAAe,QAAQ,OAAA,EAAsB;AAClD,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,OAAA,CAAQ,OAAA;AAC7B,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA,EAAG,KAAA;AAE/C,IAAA,MAAM,QAAA,GAAW,aAAa,IAAA,EAAK;AAEnC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,EAAS,OAAO,CAAA;AACvC,QAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAM,SAAA,CAAU,OAAO,MAAM,CAAA;AAEjD,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAA,EAAkB,KAAK,CAAA;AAC5C,QAAA,IAAI,QAAQ,GAAA,EAAK,QAAA,CAAS,QAAQ,GAAA,CAAI,kBAAA,EAAoB,QAAQ,GAAG,CAAA;AAAA,MACvE,CAAA,CAAA,MAAQ;AAEN,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,YAAA,EAAc,CAAA,EAAG,UAAU,CAAA,kCAAA,CAAoC,CAAA;AAAA,MACtF;AAAA,IACF;AAGA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA;AAC3D,MAAA,MAAM,QAAA,GAAW,aAAa,IAAA,CAAK,CAAC,MAAM,QAAA,CAAS,UAAA,CAAW,CAAC,CAAC,CAAA;AAChE,MAAA,MAAM,WAAA,GAAc,gBAAgB,IAAA,CAAK,CAAC,MAAM,QAAA,CAAS,UAAA,CAAW,CAAC,CAAC,CAAA;AAEtE,MAAA,IAAI,WAAA,IAAe,CAAC,QAAA,IAAY,CAAC,aAAA,EAAe;AAC9C,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAM;AAClC,QAAA,GAAA,CAAI,QAAA,GAAW,QAAA;AACf,QAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AACzC,QAAA,OAAO,YAAA,CAAa,SAAS,GAAG,CAAA;AAAA,MAClC;AAAA,IACF;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;AAyBO,SAAS,kBAAkB,OAAA,EAA8B;AAC9D,EAAA,OAAO,yBAAyB,OAAO,CAAA;AACzC;AAGO,IAAM,cAAc,iBAAA","file":"next-proxy.js","sourcesContent":["/**\n * Next.js middleware for Azirid Access.\n *\n * Validates the `__session` JWT cookie using JWKS and provides\n * route protection and token forwarding for server components.\n *\n * @example Next.js 16+ (proxy.ts)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Next.js 16+ with custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @packageDocumentation\n */\n\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { createRemoteJWKSet, jwtVerify } from 'jose'\n\n// ─── Options ────────────────────────────────────────────────\n\nexport interface AziridProxyOptions {\n /** Cookie name that carries the access token JWT (default: \"__session\") */\n cookieName?: string\n /** Routes that require authentication (matched via startsWith) */\n protectedRoutes?: string[]\n /** Route to redirect to when not authenticated (default: \"/login\") */\n loginUrl?: string\n /** Routes that are always public (default: [\"/login\", \"/signup\"]) */\n publicRoutes?: string[]\n /** JWKS endpoint URL. Default: `${AZIRID_API_URL}/v1/.well-known/jwks.json` */\n jwksUrl?: string\n}\n\n// ─── JWKS (lazy-initialized, cached by jose internally) ─────\n\nlet jwks: ReturnType<typeof createRemoteJWKSet> | null = null\n\nfunction getJwks(jwksUrl?: string): ReturnType<typeof createRemoteJWKSet> {\n if (jwks) return jwks\n const url =\n jwksUrl ??\n `${process.env.AZIRID_API_URL ?? 'https://api.azirid.com'}/v1/.well-known/jwks.json`\n jwks = createRemoteJWKSet(new URL(url))\n return jwks\n}\n\n// ─── Middleware logic ───────────────────────────────────────\n\nfunction createRequestInterceptor(options?: AziridProxyOptions) {\n const cookieName = options?.cookieName ?? '__session'\n const loginUrl = options?.loginUrl ?? '/login'\n const defaultPublic = ['/login', '/signup', '/auth/handoff']\n const publicRoutes = options?.publicRoutes\n ? [...new Set([...options.publicRoutes, '/auth/handoff'])]\n : defaultPublic\n const protectedRoutes = options?.protectedRoutes\n\n return async function handler(request: NextRequest) {\n const { pathname } = request.nextUrl\n const token = request.cookies.get(cookieName)?.value\n\n const response = NextResponse.next()\n\n if (token) {\n try {\n const keySet = getJwks(options?.jwksUrl)\n const { payload } = await jwtVerify(token, keySet)\n // Valid token — expose for server components via headers\n response.headers.set('x-azirid-token', token)\n if (payload.sub) response.headers.set('x-azirid-user-id', payload.sub)\n } catch {\n // Invalid or expired JWT — clear corrupted cookie\n response.headers.set('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`)\n }\n }\n\n // Route protection (only when protectedRoutes is configured)\n if (protectedRoutes) {\n const hasValidToken = response.headers.has('x-azirid-token')\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r))\n const isProtected = protectedRoutes.some((r) => pathname.startsWith(r))\n\n if (isProtected && !isPublic && !hasValidToken) {\n const url = request.nextUrl.clone()\n url.pathname = loginUrl\n url.searchParams.set('redirect', pathname)\n return NextResponse.redirect(url)\n }\n }\n\n return response\n }\n}\n\n// ─── Proxy (Next.js 16+) ────────────────────────────────────\n\n/**\n * Create a customized Azirid middleware for Next.js 16+.\n *\n * @example Default (one line)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n */\nexport function createAziridProxy(options?: AziridProxyOptions) {\n return createRequestInterceptor(options)\n}\n\n/** Default proxy export for Next.js 16+ (no route protection, only validates + forwards token) */\nexport const aziridProxy = createAziridProxy()\n"]}
1
+ {"version":3,"sources":["../src/next-proxy.ts"],"names":[],"mappings":"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","file":"next-proxy.js","sourcesContent":["/**\n * Next.js middleware for Azirid Access.\n *\n * Validates the `__session` JWT cookie using JWKS and provides\n * route protection and token forwarding for server components.\n *\n * @example Next.js 16+ (proxy.ts)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Next.js 16+ with custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @packageDocumentation\n */\n\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { createRemoteJWKSet, jwtVerify } from 'jose'\n\n// ─── Options ────────────────────────────────────────────────\n\nexport interface AziridProxyOptions {\n /** Cookie name that carries the access token JWT (default: \"__session\") */\n cookieName?: string\n /** Routes that require authentication (matched via startsWith) */\n protectedRoutes?: string[]\n /** Route to redirect to when not authenticated (default: \"/login\") */\n loginUrl?: string\n /** Routes that are always public (default: [\"/login\", \"/signup\"]) */\n publicRoutes?: string[]\n /** JWKS endpoint URL. Default: `${AZIRID_API_URL}/v1/.well-known/jwks.json` */\n jwksUrl?: string\n}\n\n// ─── JWKS (lazy-initialized, cached by jose internally) ─────\n\nlet jwks: ReturnType<typeof createRemoteJWKSet> | null = null\n\nfunction getJwks(jwksUrl?: string): ReturnType<typeof createRemoteJWKSet> {\n if (jwks) return jwks\n const url =\n jwksUrl ??\n `${process.env.AZIRID_API_URL ?? 'https://api.azirid.com'}/v1/.well-known/jwks.json`\n jwks = createRemoteJWKSet(new URL(url))\n return jwks\n}\n\n// ─── Middleware logic ───────────────────────────────────────\n\nfunction createRequestInterceptor(options?: AziridProxyOptions) {\n const cookieName = options?.cookieName ?? '__session'\n const loginUrl = options?.loginUrl ?? '/login'\n const defaultPublic = ['/login', '/signup', '/auth/handoff']\n const publicRoutes = options?.publicRoutes\n ? [...new Set([...options.publicRoutes, '/auth/handoff'])]\n : defaultPublic\n const protectedRoutes = options?.protectedRoutes\n\n return async function handler(request: NextRequest) {\n const { pathname } = request.nextUrl\n const token = request.cookies.get(cookieName)?.value\n\n const response = NextResponse.next()\n\n if (token) {\n try {\n const keySet = getJwks(options?.jwksUrl)\n const { payload } = await jwtVerify(token, keySet)\n // Valid token — expose for server components via headers\n response.headers.set('x-azirid-token', token)\n if (payload.sub) response.headers.set('x-azirid-user-id', payload.sub)\n } catch (err) {\n // Invalid or expired JWT — clear corrupted cookie\n if (typeof console !== 'undefined') {\n console.warn('[azirid-proxy] JWT validation failed:', err instanceof Error ? err.message : err)\n }\n response.headers.set('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`)\n }\n }\n\n // Route protection (only when protectedRoutes is configured)\n if (protectedRoutes) {\n const hasValidToken = response.headers.has('x-azirid-token')\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r))\n const isProtected = protectedRoutes.some((r) => pathname.startsWith(r))\n\n if (isProtected && !isPublic && !hasValidToken) {\n const url = request.nextUrl.clone()\n url.pathname = loginUrl\n url.searchParams.set('redirect', pathname)\n return NextResponse.redirect(url)\n }\n }\n\n return response\n }\n}\n\n// ─── Proxy (Next.js 16+) ────────────────────────────────────\n\n/**\n * Create a customized Azirid middleware for Next.js 16+.\n *\n * @example Default (one line)\n * ```ts\n * // proxy.ts\n * export { aziridProxy as proxy } from \"azirid-react/next/proxy\";\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n *\n * @example Custom options\n * ```ts\n * // proxy.ts\n * import { createAziridProxy } from \"azirid-react/next/proxy\";\n * export const proxy = createAziridProxy({\n * protectedRoutes: [\"/dashboard\"],\n * loginUrl: \"/login\",\n * });\n * export const config = { matcher: [\"/((?!_next|favicon.ico|api/).*)\"] };\n * ```\n */\nexport function createAziridProxy(options?: AziridProxyOptions) {\n return createRequestInterceptor(options)\n}\n\n/** Default proxy export for Next.js 16+ (no route protection, only validates + forwards token) */\nexport const aziridProxy = createAziridProxy()\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azirid-react",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "Authentication components for React and Next.js — Login, Register, powered by TanStack Query and Zod.",
5
5
  "author": "Azirid",
6
6
  "license": "MIT",