azirid-react 0.14.3 → 0.14.5

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
@@ -1432,6 +1432,11 @@ export const proxy = createAziridProxy({
1432
1432
  // The original URL is preserved as ?redirect= so you can send them back after login.
1433
1433
  loginUrl: '/login',
1434
1434
 
1435
+ // Where to redirect authenticated users when they visit a public route.
1436
+ // E.g. user is logged in and visits /login → redirected to /dashboard.
1437
+ // Your app handles role-based routing from there.
1438
+ dashboardUrl: '/dashboard',
1439
+
1435
1440
  // Routes that are ALWAYS public, even if they match a protectedRoute.
1436
1441
  // Default: ['/login', '/signup', '/auth/handoff']
1437
1442
  publicRoutes: [
@@ -1452,12 +1457,20 @@ export const config = {
1452
1457
 
1453
1458
  #### How it works
1454
1459
 
1460
+ **Unauthenticated user visits protected route:**
1455
1461
  1. User visits `/dashboard/settings` without a valid token
1456
1462
  2. Middleware checks: is `/dashboard/settings` protected? Yes (`startsWith('/dashboard')`)
1457
1463
  3. Middleware checks: is it public? No
1458
1464
  4. Middleware redirects to `/login?redirect=/dashboard/settings`
1459
1465
  5. After login, you can read `?redirect=` and send them back
1460
1466
 
1467
+ **Authenticated user visits public route (with `dashboardUrl`):**
1468
+ 1. User is logged in and visits `/login`
1469
+ 2. Middleware validates the JWT — valid
1470
+ 3. Middleware checks: is `/login` a public route? Yes
1471
+ 4. `dashboardUrl` is set → redirects to `/dashboard`
1472
+ 5. Your app handles role-based routing from there (e.g. redirect admins to `/admin`)
1473
+
1461
1474
  Routes not listed in `protectedRoutes` are accessible to everyone (e.g. `/`, `/pricing`, `/about`).
1462
1475
 
1463
1476
  #### Options
@@ -1466,9 +1479,9 @@ Routes not listed in `protectedRoutes` are accessible to everyone (e.g. `/`, `/p
1466
1479
  | ------------------ | ---------- | -------------------------------- | ------------------------------------------------------------- |
1467
1480
  | `protectedRoutes` | `string[]` | — | Routes that require auth (matched with `startsWith`) |
1468
1481
  | `loginUrl` | `string` | `"/login"` | Redirect target for unauthenticated users |
1482
+ | `dashboardUrl` | `string` | — | Redirect authenticated users away from public routes here |
1469
1483
  | `publicRoutes` | `string[]` | `["/login", "/signup", "/auth/handoff"]` | Always-accessible routes, even if inside a protected path |
1470
1484
  | `cookieName` | `string` | `"__session"` | Cookie carrying the access token JWT |
1471
- | `jwksUrl` | `string` | auto | Override JWKS endpoint URL |
1472
1485
 
1473
1486
  ---
1474
1487
 
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.3";
4856
+ var SDK_VERSION = "0.14.5";
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.3";
4854
+ var SDK_VERSION = "0.14.5";
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
@@ -4,39 +4,68 @@ var server = require('next/server');
4
4
  var jose = require('jose');
5
5
 
6
6
  // src/next-proxy.ts
7
- var jwks = null;
8
- function getJwks(jwksUrl) {
9
- if (jwks) return jwks;
10
- const url = jwksUrl ?? `${process.env.AZIRID_API_URL ?? "https://api.azirid.com"}/v1/.well-known/jwks.json`;
11
- jwks = jose.createRemoteJWKSet(new URL(url));
12
- return jwks;
7
+ var keyCache = /* @__PURE__ */ new Map();
8
+ var CACHE_TTL = 5 * 60 * 1e3;
9
+ function extractKid(token) {
10
+ try {
11
+ const headerB64 = token.split(".")[0];
12
+ const header = JSON.parse(
13
+ typeof Buffer !== "undefined" ? Buffer.from(headerB64, "base64url").toString() : atob(headerB64.replace(/-/g, "+").replace(/_/g, "/"))
14
+ );
15
+ return header.kid ?? null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ async function resolveKey(kid, apiUrl) {
21
+ const cached = keyCache.get(kid);
22
+ if (cached && Date.now() - cached.cachedAt < CACHE_TTL) return cached.key;
23
+ const res = await fetch(`${apiUrl}/v1/.well-known/jwks/${kid}.json`);
24
+ if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`);
25
+ const { keys } = await res.json();
26
+ if (!keys?.length) throw new Error(`No key found for kid: ${kid}`);
27
+ const key = await jose.importJWK(keys[0], "RS256");
28
+ keyCache.set(kid, { key, cachedAt: Date.now() });
29
+ return key;
13
30
  }
14
31
  function createRequestInterceptor(options) {
15
32
  const cookieName = options?.cookieName ?? "__session";
16
33
  const loginUrl = options?.loginUrl ?? "/login";
34
+ const apiUrl = (options?.apiUrl ?? process.env.AZIRID_API_URL ?? "https://api.azirid.com").replace(/\/$/, "");
17
35
  const defaultPublic = ["/login", "/signup", "/auth/handoff"];
18
36
  const publicRoutes = options?.publicRoutes ? [.../* @__PURE__ */ new Set([...options.publicRoutes, "/auth/handoff"])] : defaultPublic;
19
37
  const protectedRoutes = options?.protectedRoutes;
38
+ const dashboardUrl = options?.dashboardUrl;
20
39
  return async function handler(request) {
21
40
  const { pathname } = request.nextUrl;
22
41
  const token = request.cookies.get(cookieName)?.value;
23
42
  const response = server.NextResponse.next();
24
43
  if (token) {
25
44
  try {
26
- const keySet = getJwks(options?.jwksUrl);
27
- const { payload } = await jose.jwtVerify(token, keySet);
45
+ const kid = extractKid(token);
46
+ if (!kid) throw new Error("JWT missing kid");
47
+ const key = await resolveKey(kid, apiUrl);
48
+ const { payload } = await jose.jwtVerify(token, key);
28
49
  response.headers.set("x-azirid-token", token);
29
50
  if (payload.sub) response.headers.set("x-azirid-user-id", payload.sub);
30
51
  } catch (err) {
31
52
  if (typeof console !== "undefined") {
32
- console.warn("[azirid-proxy] JWT validation failed:", err instanceof Error ? err.message : err);
53
+ console.warn(
54
+ "[azirid-proxy] JWT validation failed:",
55
+ err instanceof Error ? err.message : err
56
+ );
33
57
  }
34
58
  response.headers.set("Set-Cookie", `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
35
59
  }
36
60
  }
61
+ const hasValidToken = response.headers.has("x-azirid-token");
62
+ const isPublic = publicRoutes.some((r) => pathname.startsWith(r));
63
+ if (dashboardUrl && hasValidToken && isPublic && pathname !== dashboardUrl) {
64
+ const url = request.nextUrl.clone();
65
+ url.pathname = dashboardUrl;
66
+ return server.NextResponse.redirect(url);
67
+ }
37
68
  if (protectedRoutes) {
38
- const hasValidToken = response.headers.has("x-azirid-token");
39
- const isPublic = publicRoutes.some((r) => pathname.startsWith(r));
40
69
  const isProtected = protectedRoutes.some((r) => pathname.startsWith(r));
41
70
  if (isProtected && !isPublic && !hasValidToken) {
42
71
  const url = request.nextUrl.clone();
@@ -1 +1 @@
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"]}
1
+ {"version":3,"sources":["../src/next-proxy.ts"],"names":["importJWK","NextResponse","jwtVerify"],"mappings":";;;;;;AAkDA,IAAM,QAAA,uBAAe,GAAA,EAAkD;AACvE,IAAM,SAAA,GAAY,IAAI,EAAA,GAAK,GAAA;AAE3B,SAAS,WAAW,KAAA,EAA8B;AAChD,EAAA,IAAI;AACF,IAAA,MAAM,SAAA,GAAY,KAAA,CAAM,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AACpC,IAAA,MAAM,SAAS,IAAA,CAAK,KAAA;AAAA,MAClB,OAAO,MAAA,KAAW,WAAA,GACd,OAAO,IAAA,CAAK,SAAA,EAAW,WAAW,CAAA,CAAE,QAAA,KACpC,IAAA,CAAK,SAAA,CAAU,QAAQ,IAAA,EAAM,GAAG,EAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC;AAAA,KAC1D;AACA,IAAA,OAAO,OAAO,GAAA,IAAO,IAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,eAAe,UAAA,CAAW,KAAa,MAAA,EAAoC;AACzE,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AAC/B,EAAA,IAAI,MAAA,IAAU,KAAK,GAAA,EAAI,GAAI,OAAO,QAAA,GAAW,SAAA,SAAkB,MAAA,CAAO,GAAA;AAEtE,EAAA,MAAM,MAAM,MAAM,KAAA,CAAM,GAAG,MAAM,CAAA,qBAAA,EAAwB,GAAG,CAAA,KAAA,CAAO,CAAA;AACnE,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAE/D,EAAA,MAAM,EAAE,IAAA,EAAK,GAAK,MAAM,IAAI,IAAA,EAAK;AACjC,EAAA,IAAI,CAAC,MAAM,MAAA,EAAQ,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,GAAG,CAAA,CAAE,CAAA;AAEjE,EAAA,MAAM,MAAO,MAAMA,cAAA,CAAU,IAAA,CAAK,CAAC,GAAG,OAAO,CAAA;AAC7C,EAAA,QAAA,CAAS,GAAA,CAAI,KAAK,EAAE,GAAA,EAAK,UAAU,IAAA,CAAK,GAAA,IAAO,CAAA;AAC/C,EAAA,OAAO,GAAA;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,MAAA,GAAA,CACJ,SAAS,MAAA,IACT,OAAA,CAAQ,IAAI,cAAA,IACZ,wBAAA,EACA,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACnB,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;AACjC,EAAA,MAAM,eAAe,OAAA,EAAS,YAAA;AAE9B,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,GAAA,GAAM,WAAW,KAAK,CAAA;AAC5B,QAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAE3C,QAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,GAAA,EAAK,MAAM,CAAA;AACxC,QAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAMC,cAAA,CAAU,OAAO,GAAG,CAAA;AAG9C,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,SAAS,GAAA,EAAK;AAEZ,QAAA,IAAI,OAAO,YAAY,WAAA,EAAa;AAClC,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,uCAAA;AAAA,YACA,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,WACvC;AAAA,QACF;AACA,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,YAAA,EAAc,CAAA,EAAG,UAAU,CAAA,kCAAA,CAAoC,CAAA;AAAA,MACtF;AAAA,IACF;AAEA,IAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA;AAC3D,IAAA,MAAM,QAAA,GAAW,aAAa,IAAA,CAAK,CAAC,MAAM,QAAA,CAAS,UAAA,CAAW,CAAC,CAAC,CAAA;AAGhE,IAAA,IAAI,YAAA,IAAgB,aAAA,IAAiB,QAAA,IAAY,QAAA,KAAa,YAAA,EAAc;AAC1E,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAM;AAClC,MAAA,GAAA,CAAI,QAAA,GAAW,YAAA;AACf,MAAA,OAAOD,mBAAA,CAAa,SAAS,GAAG,CAAA;AAAA,IAClC;AAGA,IAAA,IAAI,eAAA,EAAiB;AACnB,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,OAAOA,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 by resolving the signing key\n * via the kid in the JWT header. Provides route protection and token\n * 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 { importJWK, jwtVerify, type JWK } 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 /** Redirect authenticated users away from public routes (e.g. \"/dashboard\"). If set, users with a valid token visiting a publicRoute will be redirected here. */\n dashboardUrl?: string\n /** Override the Azirid API URL for key resolution (default: https://api.azirid.com) */\n apiUrl?: string\n}\n\n// ─── Key resolver (cached, resolves by kid) ─────────────────\n\nconst keyCache = new Map<string, { key: CryptoKey; cachedAt: number }>()\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\nfunction extractKid(token: string): string | null {\n try {\n const headerB64 = token.split('.')[0]\n const header = JSON.parse(\n typeof Buffer !== 'undefined'\n ? Buffer.from(headerB64, 'base64url').toString()\n : atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')),\n )\n return header.kid ?? null\n } catch {\n return null\n }\n}\n\nasync function resolveKey(kid: string, apiUrl: string): Promise<CryptoKey> {\n const cached = keyCache.get(kid)\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL) return cached.key\n\n const res = await fetch(`${apiUrl}/v1/.well-known/jwks/${kid}.json`)\n if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`)\n\n const { keys } = (await res.json()) as { keys: JWK[] }\n if (!keys?.length) throw new Error(`No key found for kid: ${kid}`)\n\n const key = (await importJWK(keys[0], 'RS256')) as CryptoKey\n keyCache.set(kid, { key, cachedAt: Date.now() })\n return key\n}\n\n// ─── Middleware logic ───────────────────────────────────────\n\nfunction createRequestInterceptor(options?: AziridProxyOptions) {\n const cookieName = options?.cookieName ?? '__session'\n const loginUrl = options?.loginUrl ?? '/login'\n const apiUrl = (\n options?.apiUrl ??\n process.env.AZIRID_API_URL ??\n 'https://api.azirid.com'\n ).replace(/\\/$/, '')\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 const dashboardUrl = options?.dashboardUrl\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 kid = extractKid(token)\n if (!kid) throw new Error('JWT missing kid')\n\n const key = await resolveKey(kid, apiUrl)\n const { payload } = await jwtVerify(token, key)\n\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(\n '[azirid-proxy] JWT validation failed:',\n err instanceof Error ? err.message : err,\n )\n }\n response.headers.set('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`)\n }\n }\n\n const hasValidToken = response.headers.has('x-azirid-token')\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r))\n\n // Redirect authenticated users away from public routes (e.g. login → dashboard)\n if (dashboardUrl && hasValidToken && isPublic && pathname !== dashboardUrl) {\n const url = request.nextUrl.clone()\n url.pathname = dashboardUrl\n return NextResponse.redirect(url)\n }\n\n // Route protection (only when protectedRoutes is configured)\n if (protectedRoutes) {\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"]}
@@ -3,8 +3,9 @@ import { NextRequest, NextResponse } from 'next/server';
3
3
  /**
4
4
  * Next.js middleware for Azirid Access.
5
5
  *
6
- * Validates the `__session` JWT cookie using JWKS and provides
7
- * route protection and token forwarding for server components.
6
+ * Validates the `__session` JWT cookie by resolving the signing key
7
+ * via the kid in the JWT header. Provides route protection and token
8
+ * forwarding for server components.
8
9
  *
9
10
  * @example Next.js 16+ (proxy.ts)
10
11
  * ```ts
@@ -36,8 +37,10 @@ interface AziridProxyOptions {
36
37
  loginUrl?: string;
37
38
  /** Routes that are always public (default: ["/login", "/signup"]) */
38
39
  publicRoutes?: string[];
39
- /** JWKS endpoint URL. Default: `${AZIRID_API_URL}/v1/.well-known/jwks.json` */
40
- jwksUrl?: string;
40
+ /** Redirect authenticated users away from public routes (e.g. "/dashboard"). If set, users with a valid token visiting a publicRoute will be redirected here. */
41
+ dashboardUrl?: string;
42
+ /** Override the Azirid API URL for key resolution (default: https://api.azirid.com) */
43
+ apiUrl?: string;
41
44
  }
42
45
  /**
43
46
  * Create a customized Azirid middleware for Next.js 16+.
@@ -3,8 +3,9 @@ import { NextRequest, NextResponse } from 'next/server';
3
3
  /**
4
4
  * Next.js middleware for Azirid Access.
5
5
  *
6
- * Validates the `__session` JWT cookie using JWKS and provides
7
- * route protection and token forwarding for server components.
6
+ * Validates the `__session` JWT cookie by resolving the signing key
7
+ * via the kid in the JWT header. Provides route protection and token
8
+ * forwarding for server components.
8
9
  *
9
10
  * @example Next.js 16+ (proxy.ts)
10
11
  * ```ts
@@ -36,8 +37,10 @@ interface AziridProxyOptions {
36
37
  loginUrl?: string;
37
38
  /** Routes that are always public (default: ["/login", "/signup"]) */
38
39
  publicRoutes?: string[];
39
- /** JWKS endpoint URL. Default: `${AZIRID_API_URL}/v1/.well-known/jwks.json` */
40
- jwksUrl?: string;
40
+ /** Redirect authenticated users away from public routes (e.g. "/dashboard"). If set, users with a valid token visiting a publicRoute will be redirected here. */
41
+ dashboardUrl?: string;
42
+ /** Override the Azirid API URL for key resolution (default: https://api.azirid.com) */
43
+ apiUrl?: string;
41
44
  }
42
45
  /**
43
46
  * Create a customized Azirid middleware for Next.js 16+.
@@ -1,40 +1,69 @@
1
1
  import { NextResponse } from 'next/server';
2
- import { jwtVerify, createRemoteJWKSet } from 'jose';
2
+ import { jwtVerify, importJWK } from 'jose';
3
3
 
4
4
  // src/next-proxy.ts
5
- var jwks = null;
6
- function getJwks(jwksUrl) {
7
- if (jwks) return jwks;
8
- const url = jwksUrl ?? `${process.env.AZIRID_API_URL ?? "https://api.azirid.com"}/v1/.well-known/jwks.json`;
9
- jwks = createRemoteJWKSet(new URL(url));
10
- return jwks;
5
+ var keyCache = /* @__PURE__ */ new Map();
6
+ var CACHE_TTL = 5 * 60 * 1e3;
7
+ function extractKid(token) {
8
+ try {
9
+ const headerB64 = token.split(".")[0];
10
+ const header = JSON.parse(
11
+ typeof Buffer !== "undefined" ? Buffer.from(headerB64, "base64url").toString() : atob(headerB64.replace(/-/g, "+").replace(/_/g, "/"))
12
+ );
13
+ return header.kid ?? null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ async function resolveKey(kid, apiUrl) {
19
+ const cached = keyCache.get(kid);
20
+ if (cached && Date.now() - cached.cachedAt < CACHE_TTL) return cached.key;
21
+ const res = await fetch(`${apiUrl}/v1/.well-known/jwks/${kid}.json`);
22
+ if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`);
23
+ const { keys } = await res.json();
24
+ if (!keys?.length) throw new Error(`No key found for kid: ${kid}`);
25
+ const key = await importJWK(keys[0], "RS256");
26
+ keyCache.set(kid, { key, cachedAt: Date.now() });
27
+ return key;
11
28
  }
12
29
  function createRequestInterceptor(options) {
13
30
  const cookieName = options?.cookieName ?? "__session";
14
31
  const loginUrl = options?.loginUrl ?? "/login";
32
+ const apiUrl = (options?.apiUrl ?? process.env.AZIRID_API_URL ?? "https://api.azirid.com").replace(/\/$/, "");
15
33
  const defaultPublic = ["/login", "/signup", "/auth/handoff"];
16
34
  const publicRoutes = options?.publicRoutes ? [.../* @__PURE__ */ new Set([...options.publicRoutes, "/auth/handoff"])] : defaultPublic;
17
35
  const protectedRoutes = options?.protectedRoutes;
36
+ const dashboardUrl = options?.dashboardUrl;
18
37
  return async function handler(request) {
19
38
  const { pathname } = request.nextUrl;
20
39
  const token = request.cookies.get(cookieName)?.value;
21
40
  const response = NextResponse.next();
22
41
  if (token) {
23
42
  try {
24
- const keySet = getJwks(options?.jwksUrl);
25
- const { payload } = await jwtVerify(token, keySet);
43
+ const kid = extractKid(token);
44
+ if (!kid) throw new Error("JWT missing kid");
45
+ const key = await resolveKey(kid, apiUrl);
46
+ const { payload } = await jwtVerify(token, key);
26
47
  response.headers.set("x-azirid-token", token);
27
48
  if (payload.sub) response.headers.set("x-azirid-user-id", payload.sub);
28
49
  } catch (err) {
29
50
  if (typeof console !== "undefined") {
30
- console.warn("[azirid-proxy] JWT validation failed:", err instanceof Error ? err.message : err);
51
+ console.warn(
52
+ "[azirid-proxy] JWT validation failed:",
53
+ err instanceof Error ? err.message : err
54
+ );
31
55
  }
32
56
  response.headers.set("Set-Cookie", `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
33
57
  }
34
58
  }
59
+ const hasValidToken = response.headers.has("x-azirid-token");
60
+ const isPublic = publicRoutes.some((r) => pathname.startsWith(r));
61
+ if (dashboardUrl && hasValidToken && isPublic && pathname !== dashboardUrl) {
62
+ const url = request.nextUrl.clone();
63
+ url.pathname = dashboardUrl;
64
+ return NextResponse.redirect(url);
65
+ }
35
66
  if (protectedRoutes) {
36
- const hasValidToken = response.headers.has("x-azirid-token");
37
- const isPublic = publicRoutes.some((r) => pathname.startsWith(r));
38
67
  const isProtected = protectedRoutes.some((r) => pathname.startsWith(r));
39
68
  if (isProtected && !isPublic && !hasValidToken) {
40
69
  const url = request.nextUrl.clone();
@@ -1 +1 @@
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"]}
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 by resolving the signing key\n * via the kid in the JWT header. Provides route protection and token\n * 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 { importJWK, jwtVerify, type JWK } 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 /** Redirect authenticated users away from public routes (e.g. \"/dashboard\"). If set, users with a valid token visiting a publicRoute will be redirected here. */\n dashboardUrl?: string\n /** Override the Azirid API URL for key resolution (default: https://api.azirid.com) */\n apiUrl?: string\n}\n\n// ─── Key resolver (cached, resolves by kid) ─────────────────\n\nconst keyCache = new Map<string, { key: CryptoKey; cachedAt: number }>()\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\nfunction extractKid(token: string): string | null {\n try {\n const headerB64 = token.split('.')[0]\n const header = JSON.parse(\n typeof Buffer !== 'undefined'\n ? Buffer.from(headerB64, 'base64url').toString()\n : atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')),\n )\n return header.kid ?? null\n } catch {\n return null\n }\n}\n\nasync function resolveKey(kid: string, apiUrl: string): Promise<CryptoKey> {\n const cached = keyCache.get(kid)\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL) return cached.key\n\n const res = await fetch(`${apiUrl}/v1/.well-known/jwks/${kid}.json`)\n if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`)\n\n const { keys } = (await res.json()) as { keys: JWK[] }\n if (!keys?.length) throw new Error(`No key found for kid: ${kid}`)\n\n const key = (await importJWK(keys[0], 'RS256')) as CryptoKey\n keyCache.set(kid, { key, cachedAt: Date.now() })\n return key\n}\n\n// ─── Middleware logic ───────────────────────────────────────\n\nfunction createRequestInterceptor(options?: AziridProxyOptions) {\n const cookieName = options?.cookieName ?? '__session'\n const loginUrl = options?.loginUrl ?? '/login'\n const apiUrl = (\n options?.apiUrl ??\n process.env.AZIRID_API_URL ??\n 'https://api.azirid.com'\n ).replace(/\\/$/, '')\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 const dashboardUrl = options?.dashboardUrl\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 kid = extractKid(token)\n if (!kid) throw new Error('JWT missing kid')\n\n const key = await resolveKey(kid, apiUrl)\n const { payload } = await jwtVerify(token, key)\n\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(\n '[azirid-proxy] JWT validation failed:',\n err instanceof Error ? err.message : err,\n )\n }\n response.headers.set('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; SameSite=Lax`)\n }\n }\n\n const hasValidToken = response.headers.has('x-azirid-token')\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r))\n\n // Redirect authenticated users away from public routes (e.g. login → dashboard)\n if (dashboardUrl && hasValidToken && isPublic && pathname !== dashboardUrl) {\n const url = request.nextUrl.clone()\n url.pathname = dashboardUrl\n return NextResponse.redirect(url)\n }\n\n // Route protection (only when protectedRoutes is configured)\n if (protectedRoutes) {\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.3",
3
+ "version": "0.14.5",
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",