@logickernel/bridge 0.17.1 → 0.17.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/dist/index.cjs CHANGED
@@ -88,9 +88,21 @@ async function fetchUserRoles(options) {
88
88
  const isSecure = options.isSecure ?? process.env.NODE_ENV === "production";
89
89
  const kernelUrl = getAuthUrl();
90
90
  const cookieName = getCookieName(isSecure);
91
+ const url = `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`;
92
+ console.log("[bridge] fetchUserRoles:", {
93
+ url,
94
+ organizationId,
95
+ cookieName,
96
+ isSecure,
97
+ kernelUrl,
98
+ hasToken: !!sessionToken,
99
+ tokenLength: sessionToken?.length,
100
+ AUTH_URL: process.env.AUTH_URL || "(not set)",
101
+ BASE_URL: process.env.BASE_URL || "(not set)"
102
+ });
91
103
  try {
92
104
  const response = await fetchFn(
93
- `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,
105
+ url,
94
106
  {
95
107
  headers: {
96
108
  Cookie: `${cookieName}=${sessionToken}`
@@ -99,18 +111,46 @@ async function fetchUserRoles(options) {
99
111
  }
100
112
  );
101
113
  if (!response.ok) {
114
+ let body = "";
115
+ try {
116
+ body = await response.text();
117
+ } catch {
118
+ }
119
+ console.error("[bridge] fetchUserRoles failed:", {
120
+ status: response.status,
121
+ statusText: response.statusText,
122
+ url,
123
+ body: body.substring(0, 500)
124
+ });
102
125
  return {
103
126
  success: false,
104
- error: `Failed to fetch roles: ${response.status}`
127
+ error: `Failed to fetch roles: ${response.status} ${response.statusText} from ${url}`
105
128
  };
106
129
  }
107
130
  const data = await response.json();
108
131
  const roles = data.roles?.map((r) => r.roleName) || [];
132
+ console.log("[bridge] fetchUserRoles success:", {
133
+ url,
134
+ rolesCount: roles.length,
135
+ roles
136
+ });
109
137
  return { success: true, roles };
110
138
  } catch (error) {
139
+ const message = error instanceof Error ? error.message : "Unknown error";
140
+ const cause = error instanceof Error && error.cause ? String(error.cause) : void 0;
141
+ console.error("[bridge] fetchUserRoles exception:", {
142
+ message,
143
+ cause,
144
+ url,
145
+ kernelUrl,
146
+ cookieName,
147
+ isSecure,
148
+ errorName: error instanceof Error ? error.name : "unknown",
149
+ stack: error instanceof Error ? error.stack : void 0
150
+ });
111
151
  return {
112
152
  success: false,
113
- error: error instanceof Error ? error.message : "Unknown error"
153
+ error: `${message}${cause ? ` (cause: ${cause})` : ""} fetching ${url}`
114
154
  };
115
155
  }
116
156
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":["nextAuthDecode"],"mappings":";;;;;AAaA,SAAS,iBAAA,GAA4B;AACnC,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,GAAA,CAAI,WAAA,IAAe,yBAAA;AAE9C,EAAA,OAAO,WAAW,UAAA,CAAW,WAAW,IAAI,UAAA,CAAW,KAAA,CAAM,EAAE,CAAA,GAAI,UAAA;AACrE;AAMO,IAAM,YAAA,GAAe;AAAA,EAC1B,IAAI,MAAA,GAAiB;AACnB,IAAA,MAAM,WAAW,iBAAA,EAAkB;AAEnC,IAAA,OAAO,YAAY,QAAQ,CAAA,CAAA;AAAA,EAC7B,CAAA;AAAA,EACA,IAAI,GAAA,GAAc;AAChB,IAAA,OAAO,iBAAA,EAAkB;AAAA,EAC3B;AACF;AAMO,SAAS,UAAA,GAAqB;AACnC,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,QAAA,IAAY,OAAA,CAAQ,IAAI,QAAA,IAAY,uBAAA;AACzD;AAKO,SAAS,cAAc,QAAA,EAA2B;AACvD,EAAA,OAAO,QAAA,GAAW,YAAA,CAAa,MAAA,GAAS,YAAA,CAAa,GAAA;AACvD;AAKO,SAAS,eAAe,OAAA,EAAgC;AAC7D,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,EAAK,OAAO,KAAA;AACzB,EAAA,OAAO,OAAA,CAAQ,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,GAAA,EAAI;AACvC;AAKO,SAAS,aAAa,OAAA,EAAgC;AAC3D,EAAA,OAAO;AAAA,IACL,IAAA,EAAM;AAAA,MACJ,IAAI,OAAA,CAAQ,GAAA;AAAA,MACZ,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,IAAA,EAAO,QAAQ,IAAA,IAAmB,IAAA;AAAA,MAClC,KAAA,EAAQ,QAAQ,OAAA,IAAsB;AAAA,KACxC;AAAA,IACA,OAAA,EAAS,QAAQ,GAAA,GACb,IAAI,KAAK,OAAA,CAAQ,GAAA,GAAM,GAAI,CAAA,CAAE,WAAA,EAAY,GACzC,IAAI,IAAA,CAAK,IAAA,CAAK,KAAI,GAAI,EAAA,GAAK,KAAK,EAAA,GAAK,EAAA,GAAK,GAAI,CAAA,CAAE,WAAA;AAAY;AAAA,GAClE;AACF;AAwBA,eAAsB,kBAAA,CACpB,KAAA,EACA,MAAA,EACA,SAAA,GAAqB,KAAA,EACE;AACvB,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,eAAA,EAAgB;AAAA,EAClD;AAEA,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,gBAAA,EAAiB;AAAA,EACnD;AAEA,EAAA,IAAI;AAGF,IAAA,MAAM,OAAA,GAAU,MAAMA,UAAA,CAAe;AAAA,MACnC,KAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA,EAAM;AAAA;AAAA,KACP,CAAA;AAED,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,cAAA,EAAe;AAAA,IACjD;AAGA,IAAA,IAAI,OAAA,CAAQ,GAAA,IAAO,OAAO,OAAA,CAAQ,GAAA,KAAQ,QAAA,IAAY,OAAA,CAAQ,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,GAAA,EAAI,EAAG;AACrF,MAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,SAAA,EAAU;AAAA,IAC5C;AAEA,IAAA,MAAM,OAAA,GAAU,aAAa,OAAuB,CAAA;AACpD,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,IAAA;AAAA,MACT,OAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,SAAS,KAAA,EAAO;AAEd,IAAA,MAAM,eAAe,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC1E,IAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,YAAY,CAAA;AACxD,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,cAAA,EAAe;AAAA,EACjD;AACF;AAWO,SAAS,qBAAqB,OAAA,EAEP;AAE5B,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,GAAA,CAAI,YAAA,CAAa,MAAM,CAAA;AACpD,EAAA,IAAI,cAAc,KAAA,EAAO;AACvB,IAAA,OAAO,EAAE,KAAA,EAAO,YAAA,CAAa,KAAA,EAAO,UAAU,IAAA,EAAK;AAAA,EACrD;AAGA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,YAAA,CAAa,GAAG,CAAA;AAC9C,EAAA,IAAI,WAAW,KAAA,EAAO;AACpB,IAAA,OAAO,EAAE,KAAA,EAAO,SAAA,CAAU,KAAA,EAAO,UAAU,KAAA,EAAM;AAAA,EACnD;AAEA,EAAA,OAAO,MAAA;AACT;;;ACxGA,eAAsB,eACpB,OAAA,EAC2B;AAC3B,EAAA,MAAM,EAAE,cAAA,EAAgB,YAAA,EAAc,OAAA,GAAU,OAAM,GAAI,OAAA;AAE1D,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,QAAA,IAAa,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAC/D,EAAA,MAAM,YAAY,UAAA,EAAW;AAC7B,EAAA,MAAM,UAAA,GAAa,cAAc,QAAQ,CAAA;AAEzC,EAAA,IAAI;AACF,IAAA,MAAM,WAAW,MAAM,OAAA;AAAA,MACrB,CAAA,EAAG,SAAS,CAAA,6BAAA,EAAgC,cAAc,CAAA,MAAA,CAAA;AAAA,MAC1D;AAAA,QACE,OAAA,EAAS;AAAA,UACP,MAAA,EAAQ,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,YAAY,CAAA;AAAA,SACvC;AAAA,QACA,KAAA,EAAO;AAAA;AACT,KACF;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,KAAA;AAAA,QACT,KAAA,EAAO,CAAA,uBAAA,EAA0B,QAAA,CAAS,MAAM,CAAA;AAAA,OAClD;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAO,GAAA,CAAI,CAAC,CAAA,KAA4B,CAAA,CAAE,QAAQ,CAAA,IAAK,EAAC;AAE3E,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,KAAA,EAAM;AAAA,EAChC,SAAS,KAAA,EAAO;AACd,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA;AAAA,MACT,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KAClD;AAAA,EACF;AACF;AAKO,SAAS,UAAA,CAAW,WAAqB,aAAA,EAAkC;AAChF,EAAA,IAAI,aAAA,CAAc,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AACvC,EAAA,OAAO,cAAc,IAAA,CAAK,CAAC,SAAS,SAAA,CAAU,QAAA,CAAS,IAAI,CAAC,CAAA;AAC9D;AAKO,SAAS,WAAA,CAAY,WAAqB,aAAA,EAAkC;AACjF,EAAA,IAAI,aAAA,CAAc,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AACvC,EAAA,OAAO,cAAc,KAAA,CAAM,CAAC,SAAS,SAAA,CAAU,QAAA,CAAS,IAAI,CAAC,CAAA;AAC/D;AAKO,SAAS,OAAA,CAAQ,WAAqB,IAAA,EAAuB;AAClE,EAAA,OAAO,SAAA,CAAU,SAAS,IAAI,CAAA;AAChC;;;ACxFA,SAAS,eAAe,KAAA,EAAwB;AAC9C,EAAA,OAAO,MAAM,QAAA,CAAS,GAAG,CAAA,IAAK,KAAA,CAAM,SAAS,GAAG,CAAA;AAClD;AAQA,SAAS,wBAAA,CACP,cACA,OAAA,EAC2C;AAG3C,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,MAAM,eAAe,OAAA,CAAQ,OAAA,CAAQ,eAAA,EAAiB,CAAC,GAAG,SAAA,KAAc;AACtE,IAAA,UAAA,CAAW,KAAK,SAAS,CAAA;AACzB,IAAA,OAAO,SAAA;AAAA,EACT,CAAC,CAAA;AAED,EAAA,MAAM,KAAA,GAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,YAAY,CAAA,SAAA,CAAW,CAAA;AACpD,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,KAAA,CAAM,KAAK,CAAA;AAEtC,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,MAAM,SAAiC,EAAC;AACxC,EAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,IAAA,EAAM,KAAA,KAAU;AAClC,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA;AAC7B,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,MAAA,CAAO,IAAI,CAAA,GAAI,KAAA;AAAA,IACjB;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAO,EAAE,MAAA,EAAO;AAClB;AAWO,SAAS,iBAAA,CACd,QAAA,EACA,MAAA,EACA,QAAA,EACyF;AAIzF,EAAA,IAAI,YAAA;AACJ,EAAA,IAAI,QAAA,CAAS,UAAA,CAAW,QAAQ,CAAA,EAAG;AAEjC,IAAA,YAAA,GAAe,QAAA,CAAS,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAAA,EAC/C,CAAA,MAAO;AAEL,IAAA,YAAA,GAAe,QAAA;AAAA,EACjB;AAGA,EAAA,IAAI,CAAC,YAAA,IAAgB,YAAA,KAAiB,EAAA,EAAI;AACxC,IAAA,YAAA,GAAe,GAAA;AAAA,EACjB,CAAA,MAAA,IAAW,CAAC,YAAA,CAAa,UAAA,CAAW,GAAG,CAAA,EAAG;AAExC,IAAA,YAAA,GAAe,GAAA,GAAM,YAAA;AAAA,EACvB;AAGA,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,aAAa,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC3D,IAAA,IAAI,cAAA,CAAe,KAAK,CAAA,EAAG;AAEzB,MAAA,MAAM,KAAA,GAAQ,wBAAA,CAAyB,YAAA,EAAc,KAAK,CAAA;AAC1D,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,OAAO,EAAE,KAAA,EAAO,aAAA,EAAe,MAAA,EAAQ,MAAM,MAAA,EAAO;AAAA,MACtD;AAAA,IACF,CAAA,MAAO;AAEL,MAAA,IAAI,YAAA,CAAa,UAAA,CAAW,KAAK,CAAA,EAAG;AAClC,QAAA,OAAO,EAAE,OAAO,aAAA,EAAc;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT","file":"index.cjs","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for NextAuth v4 tokens\n * Uses NextAuth's internal decode function for guaranteed compatibility\n */\n\nimport { decode as nextAuthDecode } from \"next-auth/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to next-auth.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"next-auth.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by NextAuth v4\n * Defaults to next-auth.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n _isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n // Use NextAuth's decode function directly for session tokens\n // For session tokens, NextAuth uses an empty salt (\"\")\n const decoded = await nextAuthDecode({\n token,\n secret,\n salt: \"\", // Empty salt means session token in NextAuth\n })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n // Check if token is expired\n if (decoded.exp && typeof decoded.exp === \"number\" && decoded.exp * 1000 < Date.now()) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch (error) {\n // Log the actual error for debugging\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(\"[bridge] JWT decode error:\", errorMessage)\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n\n try {\n const response = await fetchFn(\n `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n return { success: true, roles }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n * @deprecated Use findMatchingRoute instead, which supports basePath-relative routes\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Check if a route pattern is dynamic (contains [param] syntax)\n */\nfunction isDynamicRoute(route: string): boolean {\n return route.includes(\"[\") && route.includes(\"]\")\n}\n\n/**\n * Match a dynamic route pattern against a relative pathname\n * @param relativePath - Path relative to basePath (e.g., \"/abc123/admin\")\n * @param pattern - Route pattern with [param] syntax (e.g., \"/[organization_id]/admin\")\n * @returns Match result with params if matched, null otherwise\n */\nfunction matchDynamicRoutePattern(\n relativePath: string,\n pattern: string\n): { params: Record<string, string> } | null {\n // Convert pattern to regex\n // e.g., \"/[organization_id]/admin\" -> /^\\/([^/]+)\\/admin/\n const paramNames: string[] = []\n const regexPattern = pattern.replace(/\\[([^\\]]+)\\]/g, (_, paramName) => {\n paramNames.push(paramName)\n return \"([^/]+)\"\n })\n\n const regex = new RegExp(`^${regexPattern}(?:/.*)?$`)\n const match = relativePath.match(regex)\n\n if (!match) {\n return null\n }\n\n // Extract params from match groups\n const params: Record<string, string> = {}\n paramNames.forEach((name, index) => {\n const value = match[index + 1]\n if (value !== undefined) {\n params[name] = value\n }\n })\n\n return { params }\n}\n\n/**\n * Find the first matching route for a pathname (supports both static and dynamic routes)\n * Routes are relative to basePath, and basePath is prepended during matching\n * \n * @param pathname - Full pathname (e.g., \"/app/dashboard\")\n * @param routes - Route configuration (routes are relative to basePath)\n * @param basePath - Base path for the micro-frontend (e.g., \"/app\")\n * @returns Match result with route, requiredRoles, and params (if dynamic), or undefined\n */\nexport function findMatchingRoute(\n pathname: string,\n routes: RouteConfig,\n basePath: string\n): { route: string; requiredRoles: string[]; params?: Record<string, string> } | undefined {\n // Extract relative path\n // If pathname starts with basePath, remove it (direct access)\n // If not, assume it's already relative (behind proxy that strips basePath)\n let relativePath: string\n if (pathname.startsWith(basePath)) {\n // Direct access: pathname includes basePath\n relativePath = pathname.slice(basePath.length)\n } else {\n // Behind proxy: pathname is already relative (proxy stripped basePath)\n relativePath = pathname\n }\n \n // Normalize relative path\n if (!relativePath || relativePath === \"\") {\n relativePath = \"/\"\n } else if (!relativePath.startsWith(\"/\")) {\n // Ensure it starts with /\n relativePath = \"/\" + relativePath\n }\n\n // Check each route\n for (const [route, requiredRoles] of Object.entries(routes)) {\n if (isDynamicRoute(route)) {\n // Dynamic route: match pattern\n const match = matchDynamicRoutePattern(relativePath, route)\n if (match) {\n return { route, requiredRoles, params: match.params }\n }\n } else {\n // Static route: prefix matching\n if (relativePath.startsWith(route)) {\n return { route, requiredRoles }\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/app/dashboard\", {\n * basePath: \"/app\",\n * publicRoutes: [\"/\", \"/api/health\"],\n * routes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"basePath\" | \"publicRoutes\" | \"routes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check routes (relative to basePath)\n const match = findMatchingRoute(pathname, config.routes, config.basePath)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n routes: options.routes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses BASE_URL environment variable (proxy URL) for user-facing links.\n * Points to the custom signout page at /auth/signout (not the API endpoint).\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n // Use BASE_URL (proxy) for user-facing links, fallback to localhost:7001\n const baseUrl = process.env.BASE_URL || \"http://localhost:7001\"\n // Custom signout page at /core/auth/signout (Core app's basePath + /auth/signout)\n const url = new URL(\"/core/auth/signout\", baseUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
1
+ {"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":["nextAuthDecode"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for NextAuth v4 tokens\n * Uses NextAuth's internal decode function for guaranteed compatibility\n */\n\nimport { decode as nextAuthDecode } from \"next-auth/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to next-auth.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"next-auth.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by NextAuth v4\n * Defaults to next-auth.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n _isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n // Use NextAuth's decode function directly for session tokens\n // For session tokens, NextAuth uses an empty salt (\"\")\n const decoded = await nextAuthDecode({\n token,\n secret,\n salt: \"\", // Empty salt means session token in NextAuth\n })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n // Check if token is expired\n if (decoded.exp && typeof decoded.exp === \"number\" && decoded.exp * 1000 < Date.now()) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch (error) {\n // Log the actual error for debugging\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(\"[bridge] JWT decode error:\", errorMessage)\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n const url = `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`\n\n console.log(\"[bridge] fetchUserRoles:\", {\n url,\n organizationId,\n cookieName,\n isSecure,\n kernelUrl,\n hasToken: !!sessionToken,\n tokenLength: sessionToken?.length,\n AUTH_URL: process.env.AUTH_URL || \"(not set)\",\n BASE_URL: process.env.BASE_URL || \"(not set)\",\n })\n\n try {\n const response = await fetchFn(\n url,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n let body = \"\"\n try {\n body = await response.text()\n } catch {\n // ignore body read errors\n }\n console.error(\"[bridge] fetchUserRoles failed:\", {\n status: response.status,\n statusText: response.statusText,\n url,\n body: body.substring(0, 500),\n })\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status} ${response.statusText} from ${url}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n console.log(\"[bridge] fetchUserRoles success:\", {\n url,\n rolesCount: roles.length,\n roles,\n })\n\n return { success: true, roles }\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\"\n const cause = error instanceof Error && error.cause ? String(error.cause) : undefined\n console.error(\"[bridge] fetchUserRoles exception:\", {\n message,\n cause,\n url,\n kernelUrl,\n cookieName,\n isSecure,\n errorName: error instanceof Error ? error.name : \"unknown\",\n stack: error instanceof Error ? error.stack : undefined,\n })\n return {\n success: false,\n error: `${message}${cause ? ` (cause: ${cause})` : \"\"} fetching ${url}`,\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n * @deprecated Use findMatchingRoute instead, which supports basePath-relative routes\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Check if a route pattern is dynamic (contains [param] syntax)\n */\nfunction isDynamicRoute(route: string): boolean {\n return route.includes(\"[\") && route.includes(\"]\")\n}\n\n/**\n * Match a dynamic route pattern against a relative pathname\n * @param relativePath - Path relative to basePath (e.g., \"/abc123/admin\")\n * @param pattern - Route pattern with [param] syntax (e.g., \"/[organization_id]/admin\")\n * @returns Match result with params if matched, null otherwise\n */\nfunction matchDynamicRoutePattern(\n relativePath: string,\n pattern: string\n): { params: Record<string, string> } | null {\n // Convert pattern to regex\n // e.g., \"/[organization_id]/admin\" -> /^\\/([^/]+)\\/admin/\n const paramNames: string[] = []\n const regexPattern = pattern.replace(/\\[([^\\]]+)\\]/g, (_, paramName) => {\n paramNames.push(paramName)\n return \"([^/]+)\"\n })\n\n const regex = new RegExp(`^${regexPattern}(?:/.*)?$`)\n const match = relativePath.match(regex)\n\n if (!match) {\n return null\n }\n\n // Extract params from match groups\n const params: Record<string, string> = {}\n paramNames.forEach((name, index) => {\n const value = match[index + 1]\n if (value !== undefined) {\n params[name] = value\n }\n })\n\n return { params }\n}\n\n/**\n * Find the first matching route for a pathname (supports both static and dynamic routes)\n * Routes are relative to basePath, and basePath is prepended during matching\n * \n * @param pathname - Full pathname (e.g., \"/app/dashboard\")\n * @param routes - Route configuration (routes are relative to basePath)\n * @param basePath - Base path for the micro-frontend (e.g., \"/app\")\n * @returns Match result with route, requiredRoles, and params (if dynamic), or undefined\n */\nexport function findMatchingRoute(\n pathname: string,\n routes: RouteConfig,\n basePath: string\n): { route: string; requiredRoles: string[]; params?: Record<string, string> } | undefined {\n // Extract relative path\n // If pathname starts with basePath, remove it (direct access)\n // If not, assume it's already relative (behind proxy that strips basePath)\n let relativePath: string\n if (pathname.startsWith(basePath)) {\n // Direct access: pathname includes basePath\n relativePath = pathname.slice(basePath.length)\n } else {\n // Behind proxy: pathname is already relative (proxy stripped basePath)\n relativePath = pathname\n }\n \n // Normalize relative path\n if (!relativePath || relativePath === \"\") {\n relativePath = \"/\"\n } else if (!relativePath.startsWith(\"/\")) {\n // Ensure it starts with /\n relativePath = \"/\" + relativePath\n }\n\n // Check each route\n for (const [route, requiredRoles] of Object.entries(routes)) {\n if (isDynamicRoute(route)) {\n // Dynamic route: match pattern\n const match = matchDynamicRoutePattern(relativePath, route)\n if (match) {\n return { route, requiredRoles, params: match.params }\n }\n } else {\n // Static route: prefix matching\n if (relativePath.startsWith(route)) {\n return { route, requiredRoles }\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/app/dashboard\", {\n * basePath: \"/app\",\n * publicRoutes: [\"/\", \"/api/health\"],\n * routes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"basePath\" | \"publicRoutes\" | \"routes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check routes (relative to basePath)\n const match = findMatchingRoute(pathname, config.routes, config.basePath)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n routes: options.routes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses BASE_URL environment variable (proxy URL) for user-facing links.\n * Points to the custom signout page at /auth/signout (not the API endpoint).\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n // Use BASE_URL (proxy) for user-facing links, fallback to localhost:7001\n const baseUrl = process.env.BASE_URL || \"http://localhost:7001\"\n // Custom signout page at /core/auth/signout (Core app's basePath + /auth/signout)\n const url = new URL(\"/core/auth/signout\", baseUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
package/dist/index.js CHANGED
@@ -86,9 +86,21 @@ async function fetchUserRoles(options) {
86
86
  const isSecure = options.isSecure ?? process.env.NODE_ENV === "production";
87
87
  const kernelUrl = getAuthUrl();
88
88
  const cookieName = getCookieName(isSecure);
89
+ const url = `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`;
90
+ console.log("[bridge] fetchUserRoles:", {
91
+ url,
92
+ organizationId,
93
+ cookieName,
94
+ isSecure,
95
+ kernelUrl,
96
+ hasToken: !!sessionToken,
97
+ tokenLength: sessionToken?.length,
98
+ AUTH_URL: process.env.AUTH_URL || "(not set)",
99
+ BASE_URL: process.env.BASE_URL || "(not set)"
100
+ });
89
101
  try {
90
102
  const response = await fetchFn(
91
- `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,
103
+ url,
92
104
  {
93
105
  headers: {
94
106
  Cookie: `${cookieName}=${sessionToken}`
@@ -97,18 +109,46 @@ async function fetchUserRoles(options) {
97
109
  }
98
110
  );
99
111
  if (!response.ok) {
112
+ let body = "";
113
+ try {
114
+ body = await response.text();
115
+ } catch {
116
+ }
117
+ console.error("[bridge] fetchUserRoles failed:", {
118
+ status: response.status,
119
+ statusText: response.statusText,
120
+ url,
121
+ body: body.substring(0, 500)
122
+ });
100
123
  return {
101
124
  success: false,
102
- error: `Failed to fetch roles: ${response.status}`
125
+ error: `Failed to fetch roles: ${response.status} ${response.statusText} from ${url}`
103
126
  };
104
127
  }
105
128
  const data = await response.json();
106
129
  const roles = data.roles?.map((r) => r.roleName) || [];
130
+ console.log("[bridge] fetchUserRoles success:", {
131
+ url,
132
+ rolesCount: roles.length,
133
+ roles
134
+ });
107
135
  return { success: true, roles };
108
136
  } catch (error) {
137
+ const message = error instanceof Error ? error.message : "Unknown error";
138
+ const cause = error instanceof Error && error.cause ? String(error.cause) : void 0;
139
+ console.error("[bridge] fetchUserRoles exception:", {
140
+ message,
141
+ cause,
142
+ url,
143
+ kernelUrl,
144
+ cookieName,
145
+ isSecure,
146
+ errorName: error instanceof Error ? error.name : "unknown",
147
+ stack: error instanceof Error ? error.stack : void 0
148
+ });
109
149
  return {
110
150
  success: false,
111
- error: error instanceof Error ? error.message : "Unknown error"
151
+ error: `${message}${cause ? ` (cause: ${cause})` : ""} fetching ${url}`
112
152
  };
113
153
  }
114
154
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":["nextAuthDecode"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for NextAuth v4 tokens\n * Uses NextAuth's internal decode function for guaranteed compatibility\n */\n\nimport { decode as nextAuthDecode } from \"next-auth/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to next-auth.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"next-auth.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by NextAuth v4\n * Defaults to next-auth.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n _isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n // Use NextAuth's decode function directly for session tokens\n // For session tokens, NextAuth uses an empty salt (\"\")\n const decoded = await nextAuthDecode({\n token,\n secret,\n salt: \"\", // Empty salt means session token in NextAuth\n })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n // Check if token is expired\n if (decoded.exp && typeof decoded.exp === \"number\" && decoded.exp * 1000 < Date.now()) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch (error) {\n // Log the actual error for debugging\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(\"[bridge] JWT decode error:\", errorMessage)\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n\n try {\n const response = await fetchFn(\n `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n return { success: true, roles }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n * @deprecated Use findMatchingRoute instead, which supports basePath-relative routes\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Check if a route pattern is dynamic (contains [param] syntax)\n */\nfunction isDynamicRoute(route: string): boolean {\n return route.includes(\"[\") && route.includes(\"]\")\n}\n\n/**\n * Match a dynamic route pattern against a relative pathname\n * @param relativePath - Path relative to basePath (e.g., \"/abc123/admin\")\n * @param pattern - Route pattern with [param] syntax (e.g., \"/[organization_id]/admin\")\n * @returns Match result with params if matched, null otherwise\n */\nfunction matchDynamicRoutePattern(\n relativePath: string,\n pattern: string\n): { params: Record<string, string> } | null {\n // Convert pattern to regex\n // e.g., \"/[organization_id]/admin\" -> /^\\/([^/]+)\\/admin/\n const paramNames: string[] = []\n const regexPattern = pattern.replace(/\\[([^\\]]+)\\]/g, (_, paramName) => {\n paramNames.push(paramName)\n return \"([^/]+)\"\n })\n\n const regex = new RegExp(`^${regexPattern}(?:/.*)?$`)\n const match = relativePath.match(regex)\n\n if (!match) {\n return null\n }\n\n // Extract params from match groups\n const params: Record<string, string> = {}\n paramNames.forEach((name, index) => {\n const value = match[index + 1]\n if (value !== undefined) {\n params[name] = value\n }\n })\n\n return { params }\n}\n\n/**\n * Find the first matching route for a pathname (supports both static and dynamic routes)\n * Routes are relative to basePath, and basePath is prepended during matching\n * \n * @param pathname - Full pathname (e.g., \"/app/dashboard\")\n * @param routes - Route configuration (routes are relative to basePath)\n * @param basePath - Base path for the micro-frontend (e.g., \"/app\")\n * @returns Match result with route, requiredRoles, and params (if dynamic), or undefined\n */\nexport function findMatchingRoute(\n pathname: string,\n routes: RouteConfig,\n basePath: string\n): { route: string; requiredRoles: string[]; params?: Record<string, string> } | undefined {\n // Extract relative path\n // If pathname starts with basePath, remove it (direct access)\n // If not, assume it's already relative (behind proxy that strips basePath)\n let relativePath: string\n if (pathname.startsWith(basePath)) {\n // Direct access: pathname includes basePath\n relativePath = pathname.slice(basePath.length)\n } else {\n // Behind proxy: pathname is already relative (proxy stripped basePath)\n relativePath = pathname\n }\n \n // Normalize relative path\n if (!relativePath || relativePath === \"\") {\n relativePath = \"/\"\n } else if (!relativePath.startsWith(\"/\")) {\n // Ensure it starts with /\n relativePath = \"/\" + relativePath\n }\n\n // Check each route\n for (const [route, requiredRoles] of Object.entries(routes)) {\n if (isDynamicRoute(route)) {\n // Dynamic route: match pattern\n const match = matchDynamicRoutePattern(relativePath, route)\n if (match) {\n return { route, requiredRoles, params: match.params }\n }\n } else {\n // Static route: prefix matching\n if (relativePath.startsWith(route)) {\n return { route, requiredRoles }\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/app/dashboard\", {\n * basePath: \"/app\",\n * publicRoutes: [\"/\", \"/api/health\"],\n * routes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"basePath\" | \"publicRoutes\" | \"routes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check routes (relative to basePath)\n const match = findMatchingRoute(pathname, config.routes, config.basePath)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n routes: options.routes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses BASE_URL environment variable (proxy URL) for user-facing links.\n * Points to the custom signout page at /auth/signout (not the API endpoint).\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n // Use BASE_URL (proxy) for user-facing links, fallback to localhost:7001\n const baseUrl = process.env.BASE_URL || \"http://localhost:7001\"\n // Custom signout page at /core/auth/signout (Core app's basePath + /auth/signout)\n const url = new URL(\"/core/auth/signout\", baseUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
1
+ {"version":3,"sources":["../src/jwt.ts","../src/roles.ts","../src/routes.ts"],"names":["nextAuthDecode"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Kernel Bridge JWT\n * Framework-agnostic JWT decoding for NextAuth v4 tokens\n * Uses NextAuth's internal decode function for guaranteed compatibility\n */\n\nimport { decode as nextAuthDecode } from \"next-auth/jwt\"\nimport type { Session, SessionCookie, DecodeResult, DecodedToken } from \"./types\"\n\n/**\n * Get the base cookie name from AUTH_COOKIE env var or default to next-auth.session-token\n * Strips __Secure- prefix if present, as it will be added automatically for secure cookies\n */\nfunction getBaseCookieName(): string {\n const cookieName = process.env.AUTH_COOKIE || \"next-auth.session-token\"\n // Remove __Secure- prefix if present, as we'll add it back for secure cookies\n return cookieName.startsWith(\"__Secure-\") ? cookieName.slice(10) : cookieName\n}\n\n/**\n * Cookie names used by NextAuth v4\n * Defaults to next-auth.session-token, can be overridden via AUTH_COOKIE env var\n */\nexport const COOKIE_NAMES = {\n get secure(): string {\n const baseName = getBaseCookieName()\n // Secure cookies must start with __Secure- prefix\n return `__Secure-${baseName}`\n },\n get dev(): string {\n return getBaseCookieName()\n },\n} as const\n\n/**\n * Get the AUTH_URL from environment variables\n * This is used internally for API calls to the kernel\n */\nexport function getAuthUrl(): string {\n return process.env.AUTH_URL || process.env.BASE_URL || \"http://localhost:3000\"\n}\n\n/**\n * Get the cookie name based on security mode\n */\nexport function getCookieName(isSecure: boolean): string {\n return isSecure ? COOKIE_NAMES.secure : COOKIE_NAMES.dev\n}\n\n/**\n * Check if a token is expired\n */\nexport function isTokenExpired(decoded: DecodedToken): boolean {\n if (!decoded.exp) return false\n return decoded.exp * 1000 < Date.now()\n}\n\n/**\n * Map decoded JWT payload to Session structure\n */\nexport function mapToSession(decoded: DecodedToken): Session {\n return {\n user: {\n id: decoded.sub as string,\n email: decoded.email as string,\n name: (decoded.name as string) || null,\n image: (decoded.picture as string) || null,\n },\n expires: decoded.exp\n ? new Date(decoded.exp * 1000).toISOString()\n : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // Default 30 days\n }\n}\n\n/**\n * Decode and validate a session token\n *\n * This is the core function - framework-agnostic JWT decoding.\n * The caller is responsible for extracting the token from their framework's\n * cookie/header mechanism.\n *\n * @param token - The JWT session token\n * @param secret - The AUTH_SECRET used to sign the token\n * @param isSecure - Whether the token came from a secure cookie\n * @returns DecodeResult with session data or error\n *\n * @example\n * ```typescript\n * const result = await decodeSessionToken(token, process.env.AUTH_SECRET, isSecure)\n * if (result.success) {\n * console.log(result.session.user.email)\n * } else {\n * console.error(result.error)\n * }\n * ```\n */\nexport async function decodeSessionToken(\n token: string | undefined,\n secret: string | undefined,\n _isSecure: boolean = false\n): Promise<DecodeResult> {\n if (!token) {\n return { success: false, error: \"missing_token\" }\n }\n\n if (!secret) {\n return { success: false, error: \"missing_secret\" }\n }\n\n try {\n // Use NextAuth's decode function directly for session tokens\n // For session tokens, NextAuth uses an empty salt (\"\")\n const decoded = await nextAuthDecode({\n token,\n secret,\n salt: \"\", // Empty salt means session token in NextAuth\n })\n\n if (!decoded) {\n return { success: false, error: \"decode_error\" }\n }\n\n // Check if token is expired\n if (decoded.exp && typeof decoded.exp === \"number\" && decoded.exp * 1000 < Date.now()) {\n return { success: false, error: \"expired\" }\n }\n\n const session = mapToSession(decoded as DecodedToken)\n return {\n success: true,\n session,\n decoded: decoded as DecodedToken,\n }\n } catch (error) {\n // Log the actual error for debugging\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(\"[bridge] JWT decode error:\", errorMessage)\n return { success: false, error: \"decode_error\" }\n }\n}\n\n/**\n * Extract session cookie from a cookies object (generic interface)\n *\n * This works with any object that has a `get(name)` method returning\n * `{ value: string } | undefined`, like Next.js cookies() or similar.\n *\n * @param cookies - Object with get(name) method\n * @returns SessionCookie or undefined if no session cookie found\n */\nexport function extractSessionCookie(cookies: {\n get(name: string): { value: string } | undefined\n}): SessionCookie | undefined {\n // Try secure cookie first (production with HTTPS)\n const secureCookie = cookies.get(COOKIE_NAMES.secure)\n if (secureCookie?.value) {\n return { value: secureCookie.value, isSecure: true }\n }\n\n // Fall back to non-secure cookie (development)\n const devCookie = cookies.get(COOKIE_NAMES.dev)\n if (devCookie?.value) {\n return { value: devCookie.value, isSecure: false }\n }\n\n return undefined\n}\n","/**\n * Kernel Bridge Roles\n * Role checking and permission utilities\n */\n\nimport { getCookieName, getAuthUrl } from \"./jwt\"\n\n/**\n * Options for fetching user roles from the kernel\n */\nexport interface FetchRolesOptions {\n /**\n * Organization ID to fetch roles in\n */\n organizationId: string\n\n /**\n * Session token for authentication (user ID is determined from the token)\n */\n sessionToken: string\n\n /**\n * Whether using secure cookie\n * Defaults to NODE_ENV === \"production\" if not provided\n */\n isSecure?: boolean\n\n /**\n * Custom fetch function (optional, for testing or SSR)\n */\n fetchFn?: typeof fetch\n}\n\n/**\n * Result of role fetching operation\n */\nexport type FetchRolesResult =\n | { success: true; roles: string[] }\n | { success: false; error: string }\n\n/**\n * Fetch user roles from the kernel API\n *\n * This is a framework-agnostic function that makes a fetch request\n * to the kernel's roles endpoint. Uses AUTH_URL environment variable internally.\n * The user ID is determined from the session token.\n *\n * @param options - Configuration for fetching roles\n * @returns Promise with roles array or error\n *\n * @example\n * ```typescript\n * const result = await fetchUserRoles({\n * organizationId: \"org-123\",\n * sessionToken: token,\n * // isSecure defaults to NODE_ENV === \"production\"\n * })\n * if (result.success) {\n * console.log(result.roles)\n * }\n * ```\n */\nexport async function fetchUserRoles(\n options: FetchRolesOptions\n): Promise<FetchRolesResult> {\n const { organizationId, sessionToken, fetchFn = fetch } = options\n // Default isSecure to production mode\n const isSecure = options.isSecure ?? (process.env.NODE_ENV === \"production\")\n const kernelUrl = getAuthUrl()\n const cookieName = getCookieName(isSecure)\n const url = `${kernelUrl}/core/api/user/organizations/${organizationId}/roles`\n\n console.log(\"[bridge] fetchUserRoles:\", {\n url,\n organizationId,\n cookieName,\n isSecure,\n kernelUrl,\n hasToken: !!sessionToken,\n tokenLength: sessionToken?.length,\n AUTH_URL: process.env.AUTH_URL || \"(not set)\",\n BASE_URL: process.env.BASE_URL || \"(not set)\",\n })\n\n try {\n const response = await fetchFn(\n url,\n {\n headers: {\n Cookie: `${cookieName}=${sessionToken}`,\n },\n cache: \"no-store\",\n }\n )\n\n if (!response.ok) {\n let body = \"\"\n try {\n body = await response.text()\n } catch {\n // ignore body read errors\n }\n console.error(\"[bridge] fetchUserRoles failed:\", {\n status: response.status,\n statusText: response.statusText,\n url,\n body: body.substring(0, 500),\n })\n return {\n success: false,\n error: `Failed to fetch roles: ${response.status} ${response.statusText} from ${url}`,\n }\n }\n\n const data = await response.json()\n const roles = data.roles?.map((r: { roleName: string }) => r.roleName) || []\n\n console.log(\"[bridge] fetchUserRoles success:\", {\n url,\n rolesCount: roles.length,\n roles,\n })\n\n return { success: true, roles }\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\"\n const cause = error instanceof Error && error.cause ? String(error.cause) : undefined\n console.error(\"[bridge] fetchUserRoles exception:\", {\n message,\n cause,\n url,\n kernelUrl,\n cookieName,\n isSecure,\n errorName: error instanceof Error ? error.name : \"unknown\",\n stack: error instanceof Error ? error.stack : undefined,\n })\n return {\n success: false,\n error: `${message}${cause ? ` (cause: ${cause})` : \"\"} fetching ${url}`,\n }\n }\n}\n\n/**\n * Check if user has at least one of the required roles\n */\nexport function hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true // No roles required = any authenticated user\n return requiredRoles.some((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has all of the required roles\n */\nexport function hasAllRoles(userRoles: string[], requiredRoles: string[]): boolean {\n if (requiredRoles.length === 0) return true\n return requiredRoles.every((role) => userRoles.includes(role))\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(userRoles: string[], role: string): boolean {\n return userRoles.includes(role)\n}\n\n","/**\n * Kernel Bridge Routes\n * Route matching and protection utilities\n */\n\nimport { getAuthUrl } from \"./jwt\"\nimport type { RouteConfig, RouteMatch, MicroFrontendConfig } from \"./types\"\n\n/**\n * Check if a pathname matches any route in the list\n */\nexport function matchesAnyRoute(pathname: string, routes: string[]): boolean {\n return routes.some((route) => pathname.startsWith(route))\n}\n\n/**\n * Find the first matching protected route for a pathname\n * @deprecated Use findMatchingRoute instead, which supports basePath-relative routes\n */\nexport function findMatchingProtectedRoute(\n pathname: string,\n protectedRoutes: RouteConfig\n): { route: string; requiredRoles: string[] } | undefined {\n for (const [route, roles] of Object.entries(protectedRoutes)) {\n if (pathname.startsWith(route)) {\n return { route, requiredRoles: roles }\n }\n }\n return undefined\n}\n\n/**\n * Check if a route pattern is dynamic (contains [param] syntax)\n */\nfunction isDynamicRoute(route: string): boolean {\n return route.includes(\"[\") && route.includes(\"]\")\n}\n\n/**\n * Match a dynamic route pattern against a relative pathname\n * @param relativePath - Path relative to basePath (e.g., \"/abc123/admin\")\n * @param pattern - Route pattern with [param] syntax (e.g., \"/[organization_id]/admin\")\n * @returns Match result with params if matched, null otherwise\n */\nfunction matchDynamicRoutePattern(\n relativePath: string,\n pattern: string\n): { params: Record<string, string> } | null {\n // Convert pattern to regex\n // e.g., \"/[organization_id]/admin\" -> /^\\/([^/]+)\\/admin/\n const paramNames: string[] = []\n const regexPattern = pattern.replace(/\\[([^\\]]+)\\]/g, (_, paramName) => {\n paramNames.push(paramName)\n return \"([^/]+)\"\n })\n\n const regex = new RegExp(`^${regexPattern}(?:/.*)?$`)\n const match = relativePath.match(regex)\n\n if (!match) {\n return null\n }\n\n // Extract params from match groups\n const params: Record<string, string> = {}\n paramNames.forEach((name, index) => {\n const value = match[index + 1]\n if (value !== undefined) {\n params[name] = value\n }\n })\n\n return { params }\n}\n\n/**\n * Find the first matching route for a pathname (supports both static and dynamic routes)\n * Routes are relative to basePath, and basePath is prepended during matching\n * \n * @param pathname - Full pathname (e.g., \"/app/dashboard\")\n * @param routes - Route configuration (routes are relative to basePath)\n * @param basePath - Base path for the micro-frontend (e.g., \"/app\")\n * @returns Match result with route, requiredRoles, and params (if dynamic), or undefined\n */\nexport function findMatchingRoute(\n pathname: string,\n routes: RouteConfig,\n basePath: string\n): { route: string; requiredRoles: string[]; params?: Record<string, string> } | undefined {\n // Extract relative path\n // If pathname starts with basePath, remove it (direct access)\n // If not, assume it's already relative (behind proxy that strips basePath)\n let relativePath: string\n if (pathname.startsWith(basePath)) {\n // Direct access: pathname includes basePath\n relativePath = pathname.slice(basePath.length)\n } else {\n // Behind proxy: pathname is already relative (proxy stripped basePath)\n relativePath = pathname\n }\n \n // Normalize relative path\n if (!relativePath || relativePath === \"\") {\n relativePath = \"/\"\n } else if (!relativePath.startsWith(\"/\")) {\n // Ensure it starts with /\n relativePath = \"/\" + relativePath\n }\n\n // Check each route\n for (const [route, requiredRoles] of Object.entries(routes)) {\n if (isDynamicRoute(route)) {\n // Dynamic route: match pattern\n const match = matchDynamicRoutePattern(relativePath, route)\n if (match) {\n return { route, requiredRoles, params: match.params }\n }\n } else {\n // Static route: prefix matching\n if (relativePath.startsWith(route)) {\n return { route, requiredRoles }\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Match a pathname against route configuration\n *\n * @param pathname - The URL pathname to match\n * @param config - Micro-frontend configuration\n * @returns RouteMatch indicating the type of route\n *\n * @example\n * ```typescript\n * const match = matchRoute(\"/app/dashboard\", {\n * basePath: \"/app\",\n * publicRoutes: [\"/\", \"/api/health\"],\n * routes: { \"/dashboard\": [], \"/admin\": [\"organization.owner\"] },\n * })\n * // Returns: { type: \"protected\", requiredRoles: [] }\n * ```\n */\nexport function matchRoute(\n pathname: string,\n config: Pick<MicroFrontendConfig, \"basePath\" | \"publicRoutes\" | \"routes\">\n): RouteMatch {\n // Check public routes first (if specified)\n if (config.publicRoutes && matchesAnyRoute(pathname, config.publicRoutes)) {\n return { type: \"public\" }\n }\n\n // Check routes (relative to basePath)\n const match = findMatchingRoute(pathname, config.routes, config.basePath)\n if (match) {\n return { type: \"protected\", requiredRoles: match.requiredRoles }\n }\n\n // Not explicitly configured = unprotected\n return { type: \"unprotected\" }\n}\n\n/**\n * Create a default configuration for a micro-frontend\n */\nexport function createConfig(\n options: Partial<MicroFrontendConfig> & Pick<MicroFrontendConfig, \"basePath\">\n): MicroFrontendConfig {\n return {\n basePath: options.basePath,\n routes: options.routes || {\n \"/dashboard\": [],\n \"/admin\": [\"organization.owner\"],\n },\n publicRoutes: options.publicRoutes || [],\n }\n}\n\n/**\n * Build a sign-in redirect URL\n * Uses AUTH_URL environment variable internally.\n * \n * @param callbackUrl - Optional callback URL to return to after sign-in\n */\nexport function buildSignInUrl(callbackUrl?: string): URL {\n const authUrl = getAuthUrl()\n const url = new URL(\"/core/auth/signin\", authUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n/**\n * Build a sign-out redirect URL\n * Uses BASE_URL environment variable (proxy URL) for user-facing links.\n * Points to the custom signout page at /auth/signout (not the API endpoint).\n * \n * @param callbackUrl - Optional callback URL to return to after sign-out\n */\nexport function buildSignOutUrl(callbackUrl?: string): URL {\n // Use BASE_URL (proxy) for user-facing links, fallback to localhost:7001\n const baseUrl = process.env.BASE_URL || \"http://localhost:7001\"\n // Custom signout page at /core/auth/signout (Core app's basePath + /auth/signout)\n const url = new URL(\"/core/auth/signout\", baseUrl)\n if (callbackUrl) {\n url.searchParams.set(\"callbackUrl\", callbackUrl)\n }\n return url\n}\n\n"]}
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var nextThemes = require('next-themes');
4
3
  var React2 = require('react');
5
4
  var reactSlot = require('@radix-ui/react-slot');
6
5
  var classVarianceAuthority = require('class-variance-authority');
@@ -45,7 +44,7 @@ var CollapsiblePrimitive__namespace = /*#__PURE__*/_interopNamespace(Collapsible
45
44
  var AvatarPrimitive__namespace = /*#__PURE__*/_interopNamespace(AvatarPrimitive);
46
45
  var DropdownMenuPrimitive__namespace = /*#__PURE__*/_interopNamespace(DropdownMenuPrimitive);
47
46
 
48
- // src/next/components/app-layout.tsx
47
+ // src/next/components/ui/sidebar.tsx
49
48
  var MOBILE_BREAKPOINT = 768;
50
49
  function useIsMobile() {
51
50
  const [isMobile, setIsMobile] = React2__namespace.useState(void 0);
@@ -742,8 +741,7 @@ var ICON_MAP = {
742
741
  ChevronsUpDown: LucideIcons__namespace.ChevronsUpDown,
743
742
  ChevronRight: LucideIcons__namespace.ChevronRight,
744
743
  BadgeCheck: LucideIcons__namespace.BadgeCheck,
745
- LogOut: LucideIcons__namespace.LogOut,
746
- PieChart: LucideIcons__namespace.PieChart
744
+ LogOut: LucideIcons__namespace.LogOut
747
745
  // Add more icons as needed
748
746
  };
749
747
  function getIconComponent(icon) {
@@ -1356,26 +1354,17 @@ function AppLayout({
1356
1354
  apiBaseUrl,
1357
1355
  children
1358
1356
  }) {
1359
- return /* @__PURE__ */ jsxRuntime.jsx(
1360
- nextThemes.ThemeProvider,
1361
- {
1362
- attribute: "class",
1363
- defaultTheme: "system",
1364
- enableSystem: true,
1365
- disableTransitionOnChange: true,
1366
- children: /* @__PURE__ */ jsxRuntime.jsxs(SidebarProvider, { children: [
1367
- /* @__PURE__ */ jsxRuntime.jsx(
1368
- AppSidebar,
1369
- {
1370
- user,
1371
- organizationId,
1372
- apiBaseUrl
1373
- }
1374
- ),
1375
- /* @__PURE__ */ jsxRuntime.jsx(SidebarInset, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarContent2, { children }) })
1376
- ] })
1377
- }
1378
- );
1357
+ return /* @__PURE__ */ jsxRuntime.jsxs(SidebarProvider, { children: [
1358
+ /* @__PURE__ */ jsxRuntime.jsx(
1359
+ AppSidebar,
1360
+ {
1361
+ user,
1362
+ organizationId,
1363
+ apiBaseUrl
1364
+ }
1365
+ ),
1366
+ /* @__PURE__ */ jsxRuntime.jsx(SidebarInset, { children: /* @__PURE__ */ jsxRuntime.jsx(SidebarContent2, { children }) })
1367
+ ] });
1379
1368
  }
1380
1369
 
1381
1370
  exports.AppLayout = AppLayout;