@vaultix.ai/nextjs 0.5.0 → 0.5.1
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.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -8
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +6 -5
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +6 -5
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +3 -3
- package/dist/server.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ function decodeApiUrlFromKey(pk) {
|
|
|
49
49
|
try {
|
|
50
50
|
const parts = pk.split("_");
|
|
51
51
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
52
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
52
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
53
53
|
}
|
|
54
54
|
} catch {
|
|
55
55
|
}
|
|
@@ -99,13 +99,14 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
99
99
|
const cleanUrl = req.nextUrl.clone();
|
|
100
100
|
cleanUrl.searchParams.delete("__vaultix_handshake");
|
|
101
101
|
const response = import_server.NextResponse.redirect(cleanUrl);
|
|
102
|
-
|
|
103
|
-
httpOnly: true,
|
|
102
|
+
const cookieBase = {
|
|
104
103
|
secure: true,
|
|
105
104
|
sameSite: "lax",
|
|
106
105
|
path: "/",
|
|
107
106
|
maxAge: 30 * 24 * 60 * 60
|
|
108
|
-
}
|
|
107
|
+
};
|
|
108
|
+
response.cookies.set("vaultix-session", session_jwt, { ...cookieBase, httpOnly: true });
|
|
109
|
+
response.cookies.set("vaultix-token", session_jwt, { ...cookieBase, httpOnly: false });
|
|
109
110
|
return response;
|
|
110
111
|
} catch {
|
|
111
112
|
return null;
|
|
@@ -132,7 +133,7 @@ function authMiddleware(options = {}) {
|
|
|
132
133
|
isSignedIn: false,
|
|
133
134
|
isPublicRoute: publicRoute
|
|
134
135
|
};
|
|
135
|
-
const token = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
136
|
+
const token = req.cookies.get("vaultix-session")?.value ?? req.cookies.get("vaultix-token")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
136
137
|
if (token) {
|
|
137
138
|
const verifyKey = await getVerifyKey(apiUrl);
|
|
138
139
|
if (verifyKey) {
|
|
@@ -190,7 +191,7 @@ function resolveApiUrl2() {
|
|
|
190
191
|
try {
|
|
191
192
|
const parts = pk.split("_");
|
|
192
193
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
193
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
194
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
194
195
|
}
|
|
195
196
|
} catch {
|
|
196
197
|
}
|
|
@@ -253,7 +254,7 @@ async function auth() {
|
|
|
253
254
|
};
|
|
254
255
|
}
|
|
255
256
|
const jar = await (0, import_headers.cookies)();
|
|
256
|
-
const token = jar.get("vaultix-session")?.value;
|
|
257
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
257
258
|
if (!token) {
|
|
258
259
|
return {
|
|
259
260
|
userId: null,
|
|
@@ -289,7 +290,7 @@ async function auth() {
|
|
|
289
290
|
}
|
|
290
291
|
async function currentUser() {
|
|
291
292
|
const jar = await (0, import_headers.cookies)();
|
|
292
|
-
const token = jar.get("vaultix-session")?.value;
|
|
293
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
293
294
|
if (!token) return null;
|
|
294
295
|
const apiUrl = resolveApiUrl2();
|
|
295
296
|
if (!apiUrl) return null;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/server.ts"],"sourcesContent":["// @smritix.ai/nextjs — barrel export\n// Safe to import in all contexts (client, server, middleware).\n// For server-only utilities: import from \"@smritix.ai/nextjs/server\"\n// For the middleware factory: import from \"@smritix.ai/nextjs/middleware\"\n\n// ── Middleware ────────────────────────────────────────────────────────────────\nexport { authMiddleware } from \"./middleware\";\nexport type { AuthMiddlewareOptions, AuthResult } from \"./middleware\";\n\n// ── Server helpers ────────────────────────────────────────────────────────────\n// Re-exported for convenience; callers on client must use the explicit subpath.\nexport { auth, currentOrg, currentUser, protect } from \"./server\";\nexport type { AuthObject } from \"./server\";\n\n// ── Client SDK re-exports ─────────────────────────────────────────────────────\n// VaultixProvider, hooks, and UI components — safe for Client Components.\nexport {\n OrganizationSwitcher,\n SignIn,\n SignUp,\n VaultixProvider,\n UserButton,\n useOrganization,\n useSession,\n useVaultix,\n useUser,\n} from \"@vaultix.ai/react\";\n\nexport type {\n ChallengeType,\n RiskLevel,\n SessionClaims,\n VaultixContextValue,\n VaultixOrganization,\n VaultixProviderProps,\n VaultixSession,\n VaultixUser,\n} from \"@vaultix.ai/react\";\n","// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n response.cookies.set(\"vaultix-session\", session_jwt, {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n","// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,kBAAwE;AACxE,oBAA0C;AA2CnC,IAAM,iBAAoB;AAC1B,IAAM,gBAAoB;AAC1B,IAAM,kBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAIjC,SAAS,oBAAoB,IAAoB;AAC/C,MAAI;AACF,UAAM,QAAQ,GAAG,MAAM,GAAG;AAC1B,QAAI,MAAM,UAAU,KAAK,MAAM,CAAC,MAAM,aAAa,MAAM,CAAC,MAAM,MAAM;AACpE,aAAO,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,QAAQ,OAAO,EAAE;AAAA,IACzD;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,SAAS,cAAc,SAAwC;AAC7D,MAAI,QAAQ,OAAQ,QAAO,QAAQ,OAAO,QAAQ,OAAO,EAAE;AAC3D,MAAI,QAAQ,IAAI,gBAAiB,QAAO,QAAQ,IAAI,gBAAgB,QAAQ,OAAO,EAAE;AACrF,QAAM,KAAK,QAAQ,IAAI,uCAAuC;AAC9D,SAAO,oBAAoB,EAAE;AAC/B;AAMA,IAAI,aAA2D;AAC/D,IAAI,gBAA+B;AAEnC,IAAI,YAA4B;AAChC,IAAI,YAA2B;AAE/B,eAAe,aAAa,QAAgB;AAE1C,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK;AACP,UAAM,aAAa,IAAI,QAAQ,QAAQ,IAAI;AAC3C,QAAI,aAAa,cAAc,WAAY,QAAO,EAAE,KAAK,WAAW,MAAM,SAAkB;AAC5F,gBAAY,UAAM,wBAAW,YAAY,OAAO;AAChD,gBAAY;AACZ,WAAO,EAAE,KAAK,WAAW,MAAM,SAAkB;AAAA,EACnD;AAGA,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,GAAG,MAAM;AACzB,MAAI,CAAC,cAAc,kBAAkB,SAAS;AAC5C,qBAAa,gCAAmB,IAAI,IAAI,OAAO,CAAC;AAChD,oBAAgB;AAAA,EAClB;AACA,SAAO,EAAE,KAAK,YAAY,MAAM,SAAkB;AACpD;AAIA,SAAS,SAAS,UAAkB,OAAwC;AAC1E,SAAO,MAAM;AAAA,IAAK,CAAC,SACjB,OAAO,SAAS,WACZ,aAAa,QAAQ,SAAS,WAAW,IAAI,IAC7C,KAAK,KAAK,QAAQ;AAAA,EACxB;AACF;AAIA,eAAe,gBACb,KACA,gBACA,QAC8B;AAC9B,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,2BAA2B;AAAA,MAC1D,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,iBAAiB,eAAe,CAAC;AAAA,IAC1D,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AAEpB,UAAM,EAAE,YAAY,IAAK,MAAM,IAAI,KAAK;AACxC,UAAM,WAAW,IAAI,QAAQ,MAAM;AACnC,aAAS,aAAa,OAAO,qBAAqB;AAElD,UAAM,WAAW,2BAAa,SAAS,QAAQ;AAC/C,aAAS,QAAQ,IAAI,mBAAmB,aAAa;AAAA,MACnD,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ,KAAK,KAAK,KAAK;AAAA,IACzB,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcO,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,QAAM,EAAE,eAAe,CAAC,GAAG,UAAU,IAAI;AAEzC,SAAO,eAAe,WAAW,KAAyC;AACxE,UAAM,EAAE,SAAS,IAAI,IAAI;AACzB,UAAM,cAAc,SAAS,UAAU,YAAY;AAEnD,UAAM,SAAS,cAAc,OAAO;AAGpC,UAAM,YACJ,QAAQ,cAAc,SAAS,GAAG,MAAM,kBAAkB;AAG5D,UAAM,iBAAiB,IAAI,QAAQ,aAAa,IAAI,qBAAqB;AACzE,QAAI,kBAAkB,QAAQ;AAC5B,YAAM,WAAW,MAAM,gBAAgB,KAAK,gBAAgB,MAAM;AAClE,UAAI,SAAU,QAAO;AAAA,IACvB;AAGA,QAAI,SAAqB;AAAA,MACvB,QAAQ;AAAA,MAAM,OAAO;AAAA,MAAM,SAAS;AAAA,MACpC,WAAW;AAAA,MAAM,WAAW;AAAA,MAC5B,YAAY;AAAA,MAAO,eAAe;AAAA,IACpC;AAEA,UAAM,QACJ,IAAI,QAAQ,IAAI,iBAAiB,GAAG,SACpC,cAAc,IAAI,QAAQ,IAAI,eAAe,KAAK,EAAE;AAEtD,QAAI,OAAO;AACT,YAAM,YAAY,MAAM,aAAa,MAAM;AAC3C,UAAI,WAAW;AACb,YAAI;AACF,gBAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,UAAU,KAAwC;AAAA,YAC3F,YAAY,CAAC,OAAO;AAAA,UACtB,CAAC;AACD,mBAAS;AAAA,YACP,QAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,OAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,SAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,WAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,WAAY,QAAQ,MAAM,KAAgB;AAAA,YAC1C,YAAY;AAAA,YACZ,eAAe;AAAA,UACjB;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW;AACb,YAAM,WAAW,UAAU,QAAQ,GAAG;AACtC,UAAI,SAAU,QAAO;AAAA,IACvB;AAGA,QAAI,CAAC,OAAO,cAAc,CAAC,aAAa;AACtC,YAAM,OAAO,IAAI,IAAI,WAAW,IAAI,GAAG;AACvC,WAAK,aAAa,IAAI,gBAAgB,IAAI,GAAG;AAC7C,aAAO,2BAAa,SAAS,IAAI;AAAA,IACnC;AAGA,UAAM,OAAO,IAAI,QAAQ,IAAI,OAAO;AACpC,QAAI,OAAO,QAAQ;AACjB,WAAK,IAAI,gBAAmB,OAAO,MAAM;AACzC,WAAK,IAAI,eAAmB,OAAO,SAAS,EAAE;AAC9C,WAAK,IAAI,iBAAmB,OAAO,WAAW,EAAE;AAChD,WAAK,IAAI,mBAAmB,OAAO,aAAa,EAAE;AAClD,WAAK,IAAI,mBAAmB,OAAO,aAAa,KAAK;AAAA,IACvD,OAAO;AACL,OAAC,gBAAgB,eAAe,iBAAiB,mBAAmB,iBAAiB,EAClF,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;AAAA,IAClC;AAEA,WAAO,2BAAa,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,EAAE,CAAC;AAAA,EACzD;AACF;AAEA,SAAS,cAAc,QAAwB;AAC7C,SAAO,OAAO,WAAW,SAAS,IAAI,OAAO,MAAM,CAAC,IAAI;AAC1D;;;AC/OA,qBAAiC;AACjC,wBAAyB;AACzB,IAAAA,eAA0D;AA4B1D,SAASC,iBAAwB;AAC/B,MAAI,QAAQ,IAAI,gBAAiB,QAAO,QAAQ,IAAI,gBAAgB,QAAQ,OAAO,EAAE;AACrF,QAAM,KAAK,QAAQ,IAAI,uCAAuC;AAC9D,MAAI,CAAC,GAAI,QAAO;AAChB,MAAI;AACF,UAAM,QAAQ,GAAG,MAAM,GAAG;AAC1B,QAAI,MAAM,UAAU,KAAK,MAAM,CAAC,MAAM,aAAa,MAAM,CAAC,MAAM,MAAM;AACpE,aAAO,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,QAAQ,OAAO,EAAE;AAAA,IACzD;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAIA,IAAI,cAA4D;AAChE,IAAI,iBAAgC;AACpC,IAAI,aAA4D;AAChE,IAAI,aAA4B;AAEhC,eAAe,UAAU,OAAe;AAEtC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK;AACP,UAAM,aAAa,IAAI,QAAQ,QAAQ,IAAI;AAC3C,QAAI,CAAC,cAAc,eAAe,YAAY;AAC5C,mBAAa,UAAM,yBAAW,YAAY,OAAO;AACjD,mBAAa;AAAA,IACf;AACA,QAAI;AACF,YAAM,EAAE,QAAQ,IAAI,UAAM,wBAAU,OAAO,YAAY,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC;AAChF,aAAO;AAAA,IACT,QAAQ;AAAE,aAAO;AAAA,IAAM;AAAA,EACzB;AAGA,QAAM,SAASA,eAAc;AAC7B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,GAAG,MAAM;AACzB,MAAI,CAAC,eAAe,mBAAmB,SAAS;AAC9C,sBAAc,iCAAmB,IAAI,IAAI,OAAO,CAAC;AACjD,qBAAiB;AAAA,EACnB;AACA,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,UAAM,wBAAU,OAAO,aAAa,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC;AACjF,WAAO;AAAA,EACT,QAAQ;AAAE,WAAO;AAAA,EAAM;AACzB;AAkBA,eAAsB,OAA4B;AAChD,WAAS,YAAY,YAAqB;AACxC,WAAO,CAAC,eAAwB;AAC9B,UAAI,CAAC,YAAY;AACf,cAAM,SAASA,eAAc;AAC7B,cAAM,OAAO,eAAe,SAAS,GAAG,MAAM,kBAAkB;AAChE,wCAAS,IAAI;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAGA,QAAM,IAAI,UAAM,wBAAQ;AACxB,QAAM,SAAS,EAAE,IAAI,cAAc;AACnC,MAAI,QAAQ;AACV,WAAO;AAAA,MACL;AAAA,MACA,OAAW,EAAE,IAAI,aAAa,KAAS;AAAA,MACvC,SAAW,EAAE,IAAI,eAAe,KAAO;AAAA,MACvC,WAAW,EAAE,IAAI,iBAAiB,KAAK;AAAA,MACvC,WAAY,EAAE,IAAI,iBAAiB,KAAK;AAAA,MACxC,YAAY;AAAA,MACZ,SAAS,YAAY,IAAI;AAAA,IAC3B;AAAA,EACF;AAGA,QAAM,MAAM,UAAM,wBAAQ;AAC1B,QAAM,QAAQ,IAAI,IAAI,iBAAiB,GAAG;AAC1C,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL,QAAQ;AAAA,MAAM,OAAO;AAAA,MAAM,SAAS;AAAA,MACpC,WAAW;AAAA,MAAM,WAAW;AAAA,MAAM,YAAY;AAAA,MAC9C,SAAS,YAAY,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,UAAU,KAAK;AACrC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MAAM,OAAO;AAAA,MAAM,SAAS;AAAA,MACpC,WAAW;AAAA,MAAM,WAAW;AAAA,MAAM,YAAY;AAAA,MAC9C,SAAS,YAAY,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAY,QAAQ,KAAK,KAAiB;AAAA,IAC1C,OAAY,QAAQ,KAAK,KAAiB;AAAA,IAC1C,SAAY,QAAQ,KAAK,KAAiB;AAAA,IAC1C,WAAY,QAAQ,KAAK,KAAiB;AAAA,IAC1C,WAAa,QAAQ,MAAM,KAAgB;AAAA,IAC3C,YAAY;AAAA,IACZ,SAAS,YAAY,IAAI;AAAA,EAC3B;AACF;AAkBA,eAAsB,cAA2C;AAC/D,QAAM,MAAM,UAAM,wBAAQ;AAC1B,QAAM,QAAQ,IAAI,IAAI,iBAAiB,GAAG;AAC1C,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,SAASA,eAAc;AAC7B,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,cAAc;AAAA,MAC7C,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,MAC5C,OAAO;AAAA,IACT,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,WAAO,KAAK,QAAQ;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,aAAkD;AACtE,QAAM,EAAE,MAAM,IAAI,MAAM,KAAK;AAC7B,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,SAASA,eAAc;AAC7B,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,gBAAgB,KAAK,IAAI;AAAA,MACxD,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG;AAAA,MAC7C,MAAM,EAAE,YAAY,GAAG;AAAA,IACzB,CAAgB;AAChB,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeA,eAAsB,QAAQ,YAA0C;AACtE,QAAM,UAAU,MAAM,KAAK;AAC3B,UAAQ,QAAQ,UAAU;AAC1B,SAAO;AACT;;;AF1NA,mBAUO;","names":["import_jose","resolveApiUrl"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/server.ts"],"sourcesContent":["// @smritix.ai/nextjs — barrel export\n// Safe to import in all contexts (client, server, middleware).\n// For server-only utilities: import from \"@smritix.ai/nextjs/server\"\n// For the middleware factory: import from \"@smritix.ai/nextjs/middleware\"\n\n// ── Middleware ────────────────────────────────────────────────────────────────\nexport { authMiddleware } from \"./middleware\";\nexport type { AuthMiddlewareOptions, AuthResult } from \"./middleware\";\n\n// ── Server helpers ────────────────────────────────────────────────────────────\n// Re-exported for convenience; callers on client must use the explicit subpath.\nexport { auth, currentOrg, currentUser, protect } from \"./server\";\nexport type { AuthObject } from \"./server\";\n\n// ── Client SDK re-exports ─────────────────────────────────────────────────────\n// VaultixProvider, hooks, and UI components — safe for Client Components.\nexport {\n OrganizationSwitcher,\n SignIn,\n SignUp,\n VaultixProvider,\n UserButton,\n useOrganization,\n useSession,\n useVaultix,\n useUser,\n} from \"@vaultix.ai/react\";\n\nexport type {\n ChallengeType,\n RiskLevel,\n SessionClaims,\n VaultixContextValue,\n VaultixOrganization,\n VaultixProviderProps,\n VaultixSession,\n VaultixUser,\n} from \"@vaultix.ai/react\";\n","// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n const cookieBase = {\n secure: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n };\n // httpOnly — for middleware and server-side JWT verification\n response.cookies.set(\"vaultix-session\", session_jwt, { ...cookieBase, httpOnly: true });\n // non-httpOnly — for VaultixProvider (React SDK) to read client-side\n response.cookies.set(\"vaultix-token\", session_jwt, { ...cookieBase, httpOnly: false });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n req.cookies.get(\"vaultix-token\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n","// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n"],"mappings":"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","names":["import_jose","resolveApiUrl"]}
|
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ function decodeApiUrlFromKey(pk) {
|
|
|
10
10
|
try {
|
|
11
11
|
const parts = pk.split("_");
|
|
12
12
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
13
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
13
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
14
14
|
}
|
|
15
15
|
} catch {
|
|
16
16
|
}
|
|
@@ -60,13 +60,14 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
60
60
|
const cleanUrl = req.nextUrl.clone();
|
|
61
61
|
cleanUrl.searchParams.delete("__vaultix_handshake");
|
|
62
62
|
const response = NextResponse.redirect(cleanUrl);
|
|
63
|
-
|
|
64
|
-
httpOnly: true,
|
|
63
|
+
const cookieBase = {
|
|
65
64
|
secure: true,
|
|
66
65
|
sameSite: "lax",
|
|
67
66
|
path: "/",
|
|
68
67
|
maxAge: 30 * 24 * 60 * 60
|
|
69
|
-
}
|
|
68
|
+
};
|
|
69
|
+
response.cookies.set("vaultix-session", session_jwt, { ...cookieBase, httpOnly: true });
|
|
70
|
+
response.cookies.set("vaultix-token", session_jwt, { ...cookieBase, httpOnly: false });
|
|
70
71
|
return response;
|
|
71
72
|
} catch {
|
|
72
73
|
return null;
|
|
@@ -93,7 +94,7 @@ function authMiddleware(options = {}) {
|
|
|
93
94
|
isSignedIn: false,
|
|
94
95
|
isPublicRoute: publicRoute
|
|
95
96
|
};
|
|
96
|
-
const token = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
97
|
+
const token = req.cookies.get("vaultix-session")?.value ?? req.cookies.get("vaultix-token")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
97
98
|
if (token) {
|
|
98
99
|
const verifyKey = await getVerifyKey(apiUrl);
|
|
99
100
|
if (verifyKey) {
|
|
@@ -151,7 +152,7 @@ function resolveApiUrl2() {
|
|
|
151
152
|
try {
|
|
152
153
|
const parts = pk.split("_");
|
|
153
154
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
154
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
155
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
155
156
|
}
|
|
156
157
|
} catch {
|
|
157
158
|
}
|
|
@@ -214,7 +215,7 @@ async function auth() {
|
|
|
214
215
|
};
|
|
215
216
|
}
|
|
216
217
|
const jar = await cookies();
|
|
217
|
-
const token = jar.get("vaultix-session")?.value;
|
|
218
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
218
219
|
if (!token) {
|
|
219
220
|
return {
|
|
220
221
|
userId: null,
|
|
@@ -250,7 +251,7 @@ async function auth() {
|
|
|
250
251
|
}
|
|
251
252
|
async function currentUser() {
|
|
252
253
|
const jar = await cookies();
|
|
253
|
-
const token = jar.get("vaultix-session")?.value;
|
|
254
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
254
255
|
if (!token) return null;
|
|
255
256
|
const apiUrl = resolveApiUrl2();
|
|
256
257
|
if (!apiUrl) return null;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n response.cookies.set(\"vaultix-session\", session_jwt, {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n","// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n","// @smritix.ai/nextjs — barrel export\n// Safe to import in all contexts (client, server, middleware).\n// For server-only utilities: import from \"@smritix.ai/nextjs/server\"\n// For the middleware factory: import from \"@smritix.ai/nextjs/middleware\"\n\n// ── Middleware ────────────────────────────────────────────────────────────────\nexport { authMiddleware } from \"./middleware\";\nexport type { AuthMiddlewareOptions, AuthResult } from \"./middleware\";\n\n// ── Server helpers ────────────────────────────────────────────────────────────\n// Re-exported for convenience; callers on client must use the explicit subpath.\nexport { auth, currentOrg, currentUser, protect } from \"./server\";\nexport type { AuthObject } from \"./server\";\n\n// ── Client SDK re-exports ─────────────────────────────────────────────────────\n// VaultixProvider, hooks, and UI components — safe for Client Components.\nexport {\n OrganizationSwitcher,\n SignIn,\n SignUp,\n VaultixProvider,\n UserButton,\n useOrganization,\n useSession,\n useVaultix,\n useUser,\n} from \"@vaultix.ai/react\";\n\nexport type {\n ChallengeType,\n RiskLevel,\n SessionClaims,\n VaultixContextValue,\n VaultixOrganization,\n VaultixProviderProps,\n VaultixSession,\n VaultixUser,\n} from \"@vaultix.ai/react\";\n"],"mappings":"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","names":["createRemoteJWKSet","importSPKI","jwtVerify","resolveApiUrl","importSPKI","jwtVerify","createRemoteJWKSet"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n const cookieBase = {\n secure: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n };\n // httpOnly — for middleware and server-side JWT verification\n response.cookies.set(\"vaultix-session\", session_jwt, { ...cookieBase, httpOnly: true });\n // non-httpOnly — for VaultixProvider (React SDK) to read client-side\n response.cookies.set(\"vaultix-token\", session_jwt, { ...cookieBase, httpOnly: false });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n req.cookies.get(\"vaultix-token\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n","// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n","// @smritix.ai/nextjs — barrel export\n// Safe to import in all contexts (client, server, middleware).\n// For server-only utilities: import from \"@smritix.ai/nextjs/server\"\n// For the middleware factory: import from \"@smritix.ai/nextjs/middleware\"\n\n// ── Middleware ────────────────────────────────────────────────────────────────\nexport { authMiddleware } from \"./middleware\";\nexport type { AuthMiddlewareOptions, AuthResult } from \"./middleware\";\n\n// ── Server helpers ────────────────────────────────────────────────────────────\n// Re-exported for convenience; callers on client must use the explicit subpath.\nexport { auth, currentOrg, currentUser, protect } from \"./server\";\nexport type { AuthObject } from \"./server\";\n\n// ── Client SDK re-exports ─────────────────────────────────────────────────────\n// VaultixProvider, hooks, and UI components — safe for Client Components.\nexport {\n OrganizationSwitcher,\n SignIn,\n SignUp,\n VaultixProvider,\n UserButton,\n useOrganization,\n useSession,\n useVaultix,\n useUser,\n} from \"@vaultix.ai/react\";\n\nexport type {\n ChallengeType,\n RiskLevel,\n SessionClaims,\n VaultixContextValue,\n VaultixOrganization,\n VaultixProviderProps,\n VaultixSession,\n VaultixUser,\n} from \"@vaultix.ai/react\";\n"],"mappings":"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","names":["createRemoteJWKSet","importSPKI","jwtVerify","resolveApiUrl","importSPKI","jwtVerify","createRemoteJWKSet"]}
|
package/dist/middleware.js
CHANGED
|
@@ -39,7 +39,7 @@ function decodeApiUrlFromKey(pk) {
|
|
|
39
39
|
try {
|
|
40
40
|
const parts = pk.split("_");
|
|
41
41
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
42
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
42
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
43
43
|
}
|
|
44
44
|
} catch {
|
|
45
45
|
}
|
|
@@ -89,13 +89,14 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
89
89
|
const cleanUrl = req.nextUrl.clone();
|
|
90
90
|
cleanUrl.searchParams.delete("__vaultix_handshake");
|
|
91
91
|
const response = import_server.NextResponse.redirect(cleanUrl);
|
|
92
|
-
|
|
93
|
-
httpOnly: true,
|
|
92
|
+
const cookieBase = {
|
|
94
93
|
secure: true,
|
|
95
94
|
sameSite: "lax",
|
|
96
95
|
path: "/",
|
|
97
96
|
maxAge: 30 * 24 * 60 * 60
|
|
98
|
-
}
|
|
97
|
+
};
|
|
98
|
+
response.cookies.set("vaultix-session", session_jwt, { ...cookieBase, httpOnly: true });
|
|
99
|
+
response.cookies.set("vaultix-token", session_jwt, { ...cookieBase, httpOnly: false });
|
|
99
100
|
return response;
|
|
100
101
|
} catch {
|
|
101
102
|
return null;
|
|
@@ -122,7 +123,7 @@ function authMiddleware(options = {}) {
|
|
|
122
123
|
isSignedIn: false,
|
|
123
124
|
isPublicRoute: publicRoute
|
|
124
125
|
};
|
|
125
|
-
const token = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
126
|
+
const token = req.cookies.get("vaultix-session")?.value ?? req.cookies.get("vaultix-token")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
126
127
|
if (token) {
|
|
127
128
|
const verifyKey = await getVerifyKey(apiUrl);
|
|
128
129
|
if (verifyKey) {
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n response.cookies.set(\"vaultix-session\", session_jwt, {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n const cookieBase = {\n secure: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n };\n // httpOnly — for middleware and server-side JWT verification\n response.cookies.set(\"vaultix-session\", session_jwt, { ...cookieBase, httpOnly: true });\n // non-httpOnly — for VaultixProvider (React SDK) to read client-side\n response.cookies.set(\"vaultix-token\", session_jwt, { ...cookieBase, httpOnly: false });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n req.cookies.get(\"vaultix-token\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAAwE;AACxE,oBAA0C;AA2CnC,IAAM,iBAAoB;AAC1B,IAAM,gBAAoB;AAC1B,IAAM,kBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAIjC,SAAS,oBAAoB,IAAoB;AAC/C,MAAI;AACF,UAAM,QAAQ,GAAG,MAAM,GAAG;AAC1B,QAAI,MAAM,UAAU,KAAK,MAAM,CAAC,MAAM,aAAa,MAAM,CAAC,MAAM,MAAM;AAEpE,aAAO,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,QAAQ,SAAS,EAAE,EAAE,QAAQ,OAAO,EAAE;AAAA,IAC9E;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,SAAS,cAAc,SAAwC;AAC7D,MAAI,QAAQ,OAAQ,QAAO,QAAQ,OAAO,QAAQ,OAAO,EAAE;AAC3D,MAAI,QAAQ,IAAI,gBAAiB,QAAO,QAAQ,IAAI,gBAAgB,QAAQ,OAAO,EAAE;AACrF,QAAM,KAAK,QAAQ,IAAI,uCAAuC;AAC9D,SAAO,oBAAoB,EAAE;AAC/B;AAMA,IAAI,aAA2D;AAC/D,IAAI,gBAA+B;AAEnC,IAAI,YAA4B;AAChC,IAAI,YAA2B;AAE/B,eAAe,aAAa,QAAgB;AAE1C,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK;AACP,UAAM,aAAa,IAAI,QAAQ,QAAQ,IAAI;AAC3C,QAAI,aAAa,cAAc,WAAY,QAAO,EAAE,KAAK,WAAW,MAAM,SAAkB;AAC5F,gBAAY,UAAM,wBAAW,YAAY,OAAO;AAChD,gBAAY;AACZ,WAAO,EAAE,KAAK,WAAW,MAAM,SAAkB;AAAA,EACnD;AAGA,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,GAAG,MAAM;AACzB,MAAI,CAAC,cAAc,kBAAkB,SAAS;AAC5C,qBAAa,gCAAmB,IAAI,IAAI,OAAO,CAAC;AAChD,oBAAgB;AAAA,EAClB;AACA,SAAO,EAAE,KAAK,YAAY,MAAM,SAAkB;AACpD;AAIA,SAAS,SAAS,UAAkB,OAAwC;AAC1E,SAAO,MAAM;AAAA,IAAK,CAAC,SACjB,OAAO,SAAS,WACZ,aAAa,QAAQ,SAAS,WAAW,IAAI,IAC7C,KAAK,KAAK,QAAQ;AAAA,EACxB;AACF;AAIA,eAAe,gBACb,KACA,gBACA,QAC8B;AAC9B,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,2BAA2B;AAAA,MAC1D,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,iBAAiB,eAAe,CAAC;AAAA,IAC1D,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AAEpB,UAAM,EAAE,YAAY,IAAK,MAAM,IAAI,KAAK;AACxC,UAAM,WAAW,IAAI,QAAQ,MAAM;AACnC,aAAS,aAAa,OAAO,qBAAqB;AAElD,UAAM,WAAW,2BAAa,SAAS,QAAQ;AAC/C,UAAM,aAAa;AAAA,MACjB,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ,KAAK,KAAK,KAAK;AAAA,IACzB;AAEA,aAAS,QAAQ,IAAI,mBAAmB,aAAa,EAAE,GAAG,YAAY,UAAU,KAAK,CAAC;AAEtF,aAAS,QAAQ,IAAI,iBAAiB,aAAa,EAAE,GAAG,YAAY,UAAU,MAAM,CAAC;AACrF,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcO,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,QAAM,EAAE,eAAe,CAAC,GAAG,UAAU,IAAI;AAEzC,SAAO,eAAe,WAAW,KAAyC;AACxE,UAAM,EAAE,SAAS,IAAI,IAAI;AACzB,UAAM,cAAc,SAAS,UAAU,YAAY;AAEnD,UAAM,SAAS,cAAc,OAAO;AAGpC,UAAM,YACJ,QAAQ,cAAc,SAAS,GAAG,MAAM,kBAAkB;AAG5D,UAAM,iBAAiB,IAAI,QAAQ,aAAa,IAAI,qBAAqB;AACzE,QAAI,kBAAkB,QAAQ;AAC5B,YAAM,WAAW,MAAM,gBAAgB,KAAK,gBAAgB,MAAM;AAClE,UAAI,SAAU,QAAO;AAAA,IACvB;AAGA,QAAI,SAAqB;AAAA,MACvB,QAAQ;AAAA,MAAM,OAAO;AAAA,MAAM,SAAS;AAAA,MACpC,WAAW;AAAA,MAAM,WAAW;AAAA,MAC5B,YAAY;AAAA,MAAO,eAAe;AAAA,IACpC;AAEA,UAAM,QACJ,IAAI,QAAQ,IAAI,iBAAiB,GAAG,SACpC,IAAI,QAAQ,IAAI,eAAe,GAAG,SAClC,cAAc,IAAI,QAAQ,IAAI,eAAe,KAAK,EAAE;AAEtD,QAAI,OAAO;AACT,YAAM,YAAY,MAAM,aAAa,MAAM;AAC3C,UAAI,WAAW;AACb,YAAI;AACF,gBAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,UAAU,KAAwC;AAAA,YAC3F,YAAY,CAAC,OAAO;AAAA,UACtB,CAAC;AACD,mBAAS;AAAA,YACP,QAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,OAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,SAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,WAAY,QAAQ,KAAK,KAAiB;AAAA,YAC1C,WAAY,QAAQ,MAAM,KAAgB;AAAA,YAC1C,YAAY;AAAA,YACZ,eAAe;AAAA,UACjB;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW;AACb,YAAM,WAAW,UAAU,QAAQ,GAAG;AACtC,UAAI,SAAU,QAAO;AAAA,IACvB;AAGA,QAAI,CAAC,OAAO,cAAc,CAAC,aAAa;AACtC,YAAM,OAAO,IAAI,IAAI,WAAW,IAAI,GAAG;AACvC,WAAK,aAAa,IAAI,gBAAgB,IAAI,GAAG;AAC7C,aAAO,2BAAa,SAAS,IAAI;AAAA,IACnC;AAGA,UAAM,OAAO,IAAI,QAAQ,IAAI,OAAO;AACpC,QAAI,OAAO,QAAQ;AACjB,WAAK,IAAI,gBAAmB,OAAO,MAAM;AACzC,WAAK,IAAI,eAAmB,OAAO,SAAS,EAAE;AAC9C,WAAK,IAAI,iBAAmB,OAAO,WAAW,EAAE;AAChD,WAAK,IAAI,mBAAmB,OAAO,aAAa,EAAE;AAClD,WAAK,IAAI,mBAAmB,OAAO,aAAa,KAAK;AAAA,IACvD,OAAO;AACL,OAAC,gBAAgB,eAAe,iBAAiB,mBAAmB,iBAAiB,EAClF,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;AAAA,IAClC;AAEA,WAAO,2BAAa,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,EAAE,CAAC;AAAA,EACzD;AACF;AAEA,SAAS,cAAc,QAAwB;AAC7C,SAAO,OAAO,WAAW,SAAS,IAAI,OAAO,MAAM,CAAC,IAAI;AAC1D;","names":[]}
|
package/dist/middleware.mjs
CHANGED
|
@@ -10,7 +10,7 @@ function decodeApiUrlFromKey(pk) {
|
|
|
10
10
|
try {
|
|
11
11
|
const parts = pk.split("_");
|
|
12
12
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
13
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
13
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
14
14
|
}
|
|
15
15
|
} catch {
|
|
16
16
|
}
|
|
@@ -60,13 +60,14 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
60
60
|
const cleanUrl = req.nextUrl.clone();
|
|
61
61
|
cleanUrl.searchParams.delete("__vaultix_handshake");
|
|
62
62
|
const response = NextResponse.redirect(cleanUrl);
|
|
63
|
-
|
|
64
|
-
httpOnly: true,
|
|
63
|
+
const cookieBase = {
|
|
65
64
|
secure: true,
|
|
66
65
|
sameSite: "lax",
|
|
67
66
|
path: "/",
|
|
68
67
|
maxAge: 30 * 24 * 60 * 60
|
|
69
|
-
}
|
|
68
|
+
};
|
|
69
|
+
response.cookies.set("vaultix-session", session_jwt, { ...cookieBase, httpOnly: true });
|
|
70
|
+
response.cookies.set("vaultix-token", session_jwt, { ...cookieBase, httpOnly: false });
|
|
70
71
|
return response;
|
|
71
72
|
} catch {
|
|
72
73
|
return null;
|
|
@@ -93,7 +94,7 @@ function authMiddleware(options = {}) {
|
|
|
93
94
|
isSignedIn: false,
|
|
94
95
|
isPublicRoute: publicRoute
|
|
95
96
|
};
|
|
96
|
-
const token = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
97
|
+
const token = req.cookies.get("vaultix-session")?.value ?? req.cookies.get("vaultix-token")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
97
98
|
if (token) {
|
|
98
99
|
const verifyKey = await getVerifyKey(apiUrl);
|
|
99
100
|
if (verifyKey) {
|
package/dist/middleware.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n response.cookies.set(\"vaultix-session\", session_jwt, {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n const cookieBase = {\n secure: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n };\n // httpOnly — for middleware and server-side JWT verification\n response.cookies.set(\"vaultix-session\", session_jwt, { ...cookieBase, httpOnly: true });\n // non-httpOnly — for VaultixProvider (React SDK) to read client-side\n response.cookies.set(\"vaultix-token\", session_jwt, { ...cookieBase, httpOnly: false });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n req.cookies.get(\"vaultix-token\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.js
CHANGED
|
@@ -47,7 +47,7 @@ function resolveApiUrl() {
|
|
|
47
47
|
try {
|
|
48
48
|
const parts = pk.split("_");
|
|
49
49
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
50
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
50
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
51
51
|
}
|
|
52
52
|
} catch {
|
|
53
53
|
}
|
|
@@ -110,7 +110,7 @@ async function auth() {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
const jar = await (0, import_headers.cookies)();
|
|
113
|
-
const token = jar.get("vaultix-session")?.value;
|
|
113
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
114
114
|
if (!token) {
|
|
115
115
|
return {
|
|
116
116
|
userId: null,
|
|
@@ -146,7 +146,7 @@ async function auth() {
|
|
|
146
146
|
}
|
|
147
147
|
async function currentUser() {
|
|
148
148
|
const jar = await (0, import_headers.cookies)();
|
|
149
|
-
const token = jar.get("vaultix-session")?.value;
|
|
149
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
150
150
|
if (!token) return null;
|
|
151
151
|
const apiUrl = resolveApiUrl();
|
|
152
152
|
if (!apiUrl) return null;
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/middleware.ts"],"sourcesContent":["// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n","// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n response.cookies.set(\"vaultix-session\", session_jwt, {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":["import_jose"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/middleware.ts"],"sourcesContent":["// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n","// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n const cookieBase = {\n secure: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n };\n // httpOnly — for middleware and server-side JWT verification\n response.cookies.set(\"vaultix-session\", session_jwt, { ...cookieBase, httpOnly: true });\n // non-httpOnly — for VaultixProvider (React SDK) to read client-side\n response.cookies.set(\"vaultix-token\", session_jwt, { ...cookieBase, httpOnly: false });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n req.cookies.get(\"vaultix-token\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":["import_jose"]}
|
package/dist/server.mjs
CHANGED
|
@@ -20,7 +20,7 @@ function resolveApiUrl() {
|
|
|
20
20
|
try {
|
|
21
21
|
const parts = pk.split("_");
|
|
22
22
|
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
23
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
23
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
24
24
|
}
|
|
25
25
|
} catch {
|
|
26
26
|
}
|
|
@@ -83,7 +83,7 @@ async function auth() {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
const jar = await cookies();
|
|
86
|
-
const token = jar.get("vaultix-session")?.value;
|
|
86
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
87
87
|
if (!token) {
|
|
88
88
|
return {
|
|
89
89
|
userId: null,
|
|
@@ -119,7 +119,7 @@ async function auth() {
|
|
|
119
119
|
}
|
|
120
120
|
async function currentUser() {
|
|
121
121
|
const jar = await cookies();
|
|
122
|
-
const token = jar.get("vaultix-session")?.value;
|
|
122
|
+
const token = jar.get("vaultix-session")?.value ?? jar.get("vaultix-token")?.value;
|
|
123
123
|
if (!token) return null;
|
|
124
124
|
const apiUrl = resolveApiUrl();
|
|
125
125
|
if (!apiUrl) return null;
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/middleware.ts"],"sourcesContent":["// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n","// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n return atob(parts.slice(3).join(\"_\")).replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n response.cookies.set(\"vaultix-session\", session_jwt, {\n httpOnly: true,\n secure: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":["createRemoteJWKSet","importSPKI","jwtVerify","importSPKI","jwtVerify","createRemoteJWKSet"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/middleware.ts"],"sourcesContent":["// Server-only. Import from \"@vaultix.ai/nextjs/server\".\n// Never import this in Client Components.\nimport { cookies, headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { createRemoteJWKSet, importSPKI, jwtVerify } from \"jose\";\nimport type { VaultixOrganization, VaultixUser } from \"@vaultix.ai/react\";\nimport {\n HEADER_USER_ID,\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_SESSION_ID,\n HEADER_RISK_LEVEL,\n} from \"./middleware\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthObject {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: \"low\" | \"medium\" | \"high\" | \"critical\" | null;\n isSignedIn: boolean;\n /**\n * Throws a redirect if the user is not authenticated.\n * Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())\n */\n protect: (redirectTo?: string) => void;\n}\n\n// ─── API URL resolution (mirrors middleware logic) ────────────────────────────\n\nfunction resolveApiUrl(): string {\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n if (!pk) return \"\";\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\n// ─── JWT verification (server-side, no edge constraints) ─────────────────────\n\nlet _remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet _remoteJwksUrl: string | null = null;\nlet _staticKey: Awaited<ReturnType<typeof importSPKI>> | null = null;\nlet _staticPem: string | null = null;\n\nasync function verifyJwt(token: string) {\n // 1. Static PEM key (fastest, no network call)\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (!_staticKey || _staticPem !== normalized) {\n _staticKey = await importSPKI(normalized, \"RS256\");\n _staticPem = normalized;\n }\n try {\n const { payload } = await jwtVerify(token, _staticKey, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n }\n\n // 2. Remote JWKS (auto-fetched, cached)\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!_remoteJwks || _remoteJwksUrl !== jwksUrl) {\n _remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n _remoteJwksUrl = jwksUrl;\n }\n try {\n const { payload } = await jwtVerify(token, _remoteJwks, { algorithms: [\"RS256\"] });\n return payload;\n } catch { return null; }\n}\n\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state. Works in Server Components, Route Handlers,\n * and Server Actions. Falls back to verifying the session cookie directly\n * if middleware headers are not present.\n *\n * @example\n * import { auth } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const { userId, protect } = await auth();\n * protect(); // redirects to sign-in if not authenticated\n * return <div>Hello {userId}</div>;\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n function makeProtect(isSignedIn: boolean) {\n return (redirectTo?: string) => {\n if (!isSignedIn) {\n const apiUrl = resolveApiUrl();\n const dest = redirectTo ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n redirect(dest);\n }\n };\n }\n\n // ── Fast path: headers injected by authMiddleware ──────────────────────\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n if (userId) {\n return {\n userId,\n orgId: h.get(HEADER_ORG_ID) || null,\n orgRole: h.get(HEADER_ORG_ROLE) || null,\n sessionId: h.get(HEADER_SESSION_ID) || null,\n riskLevel: (h.get(HEADER_RISK_LEVEL) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n }\n\n // ── Fallback: verify session cookie directly (no middleware needed) ────\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n const payload = await verifyJwt(token);\n if (!payload) {\n return {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null, isSignedIn: false,\n protect: makeProtect(false),\n };\n }\n\n return {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: ((payload[\"risk\"] as string) ?? \"low\") as AuthObject[\"riskLevel\"],\n isSignedIn: true,\n protect: makeProtect(true),\n };\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the full user record for the currently signed-in user.\n * Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.\n * No extra env vars required.\n *\n * @example\n * import { currentUser } from \"@vaultix.ai/nextjs/server\";\n *\n * export default async function Page() {\n * const user = await currentUser();\n * if (!user) redirect(\"/sign-in\");\n * return <div>Hello {user.email}</div>;\n * }\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const jar = await cookies();\n const token = jar.get(\"vaultix-session\")?.value ?? jar.get(\"vaultix-token\")?.value;\n if (!token) return null;\n\n const apiUrl = resolveApiUrl();\n if (!apiUrl) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/me`, {\n headers: { Authorization: `Bearer ${token}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n const data = await res.json() as { user: VaultixUser };\n return data.user ?? null;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Returns the active organization for the current user.\n * Requires VAULTIX_SECRET_KEY env var.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = resolveApiUrl();\n const secret = process.env.VAULTIX_SECRET_KEY;\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/api/v1/orgs/${orgId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n } as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n\n// ─── protect() — standalone helper ───────────────────────────────────────────\n\n/**\n * Asserts the current user is authenticated. Redirects to sign-in if not.\n * Prefer calling `protect()` from the auth object returned by `auth()`.\n *\n * @example\n * import { protect } from \"@vaultix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId } = await protect();\n * return <div>{userId}</div>;\n * }\n */\nexport async function protect(redirectTo?: string): Promise<AuthObject> {\n const authObj = await auth();\n authObj.protect(redirectTo);\n return authObj;\n}\n","// Edge-runtime compatible. Uses jose for JWT verification.\nimport { createRemoteJWKSet, importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n userId: string | null;\n orgId: string | null;\n orgRole: string | null;\n sessionId: string | null;\n riskLevel: string | null;\n isSignedIn: boolean;\n isPublicRoute: boolean;\n}\n\nexport interface AuthMiddlewareOptions {\n /**\n * Routes that do not require authentication.\n * Strings: exact match or prefix. RegExps: tested against pathname.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /**\n * Where to redirect unauthenticated users.\n * Defaults to the Vaultix hosted sign-in page (decoded from publishable key).\n * Override with \"/sign-in\" to use your own page.\n */\n signInUrl?: string;\n\n /**\n * Vaultix API origin. Auto-decoded from NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY.\n * Only needed if you're not using the standard publishable key format.\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is resolved.\n * Return a NextResponse to override default behaviour.\n */\n afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names ─────────────────────────────────────────────────────────────\n\nexport const HEADER_USER_ID = \"x-vaultix-user-id\";\nexport const HEADER_ORG_ID = \"x-vaultix-org-id\";\nexport const HEADER_ORG_ROLE = \"x-vaultix-org-role\";\nexport const HEADER_SESSION_ID = \"x-vaultix-session-id\";\nexport const HEADER_RISK_LEVEL = \"x-vaultix-risk-level\";\n\n// ─── Publishable key → API URL ────────────────────────────────────────────────\n\nfunction decodeApiUrlFromKey(pk: string): string {\n try {\n const parts = pk.split(\"_\");\n if (parts.length >= 4 && parts[0] === \"vaultix\" && parts[1] === \"pk\") {\n // Strip optional \"|uniqueId\" suffix (new key format) then trailing slash\n return atob(parts.slice(3).join(\"_\")).replace(/\\|.+$/, \"\").replace(/\\/$/, \"\");\n }\n } catch {}\n return \"\";\n}\n\nfunction resolveApiUrl(options: AuthMiddlewareOptions): string {\n if (options.apiUrl) return options.apiUrl.replace(/\\/$/, \"\");\n if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\\/$/, \"\");\n const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? \"\";\n return decodeApiUrlFromKey(pk);\n}\n\n// ─── JWKS cache ───────────────────────────────────────────────────────────────\n// createRemoteJWKSet fetches and caches the key set, re-fetches on rotation.\n// Falls back to a static PEM key if VAULTIX_JWT_PUBLIC_KEY is set (backward compat).\n\nlet remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;\nlet remoteJwksUrl: string | null = null;\n\nlet staticKey: KeyLike | null = null;\nlet staticPem: string | null = null;\n\nasync function getVerifyKey(apiUrl: string) {\n // Prefer static PEM (set by env var) for zero-network-call verification\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (pem) {\n const normalized = pem.replace(/\\\\n/g, \"\\n\");\n if (staticKey && staticPem === normalized) return { key: staticKey, mode: \"static\" as const };\n staticKey = await importSPKI(normalized, \"RS256\");\n staticPem = normalized;\n return { key: staticKey, mode: \"static\" as const };\n }\n\n // Auto-fetch JWKS from the API — no env var needed\n if (!apiUrl) return null;\n const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;\n if (!remoteJwks || remoteJwksUrl !== jwksUrl) {\n remoteJwks = createRemoteJWKSet(new URL(jwksUrl));\n remoteJwksUrl = jwksUrl;\n }\n return { key: remoteJwks, mode: \"remote\" as const };\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) =>\n typeof rule === \"string\"\n ? pathname === rule || pathname.startsWith(rule)\n : rule.test(pathname),\n );\n}\n\n// ─── Handshake exchange ───────────────────────────────────────────────────────\n\nasync function handleHandshake(\n req: NextRequest,\n handshakeToken: string,\n apiUrl: string,\n): Promise<NextResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n const cleanUrl = req.nextUrl.clone();\n cleanUrl.searchParams.delete(\"__vaultix_handshake\");\n\n const response = NextResponse.redirect(cleanUrl);\n const cookieBase = {\n secure: true,\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 30 * 24 * 60 * 60,\n };\n // httpOnly — for middleware and server-side JWT verification\n response.cookies.set(\"vaultix-session\", session_jwt, { ...cookieBase, httpOnly: true });\n // non-httpOnly — for VaultixProvider (React SDK) to read client-side\n response.cookies.set(\"vaultix-token\", session_jwt, { ...cookieBase, httpOnly: false });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware ───────────────────────────────────────────────────────────\n\n/**\n * Drop-in auth middleware — works with zero config when\n * NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY is set.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/\", \"/about\"] });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const { publicRoutes = [], afterAuth } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n const apiUrl = resolveApiUrl(options);\n\n // Default sign-in URL: hosted Vaultix page decoded from publishable key\n const signInUrl =\n options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : \"/sign-in\");\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken && apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null, orgId: null, orgRole: null,\n sessionId: null, riskLevel: null,\n isSignedIn: false, isPublicRoute: publicRoute,\n };\n\n const token =\n req.cookies.get(\"vaultix-session\")?.value ??\n req.cookies.get(\"vaultix-token\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (token) {\n const verifyKey = await getVerifyKey(apiUrl);\n if (verifyKey) {\n try {\n const { payload } = await jwtVerify(token, verifyKey.key as Parameters<typeof jwtVerify>[1], {\n algorithms: [\"RS256\"],\n });\n result = {\n userId: (payload[\"uid\"] as string) ?? null,\n orgId: (payload[\"org\"] as string) ?? null,\n orgRole: (payload[\"rol\"] as string) ?? null,\n sessionId: (payload[\"sid\"] as string) ?? null,\n riskLevel: (payload[\"risk\"] as string) ?? \"low\",\n isSignedIn: true,\n isPublicRoute: publicRoute,\n };\n } catch {\n // expired / tampered\n }\n }\n }\n\n // ── Custom afterAuth hook ──────────────────────────────────────────────\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // ── Default: redirect unauthenticated to sign-in ───────────────────────\n if (!result.isSignedIn && !publicRoute) {\n const dest = new URL(signInUrl, req.url);\n dest.searchParams.set(\"redirect_url\", req.url);\n return NextResponse.redirect(dest);\n }\n\n // ── Inject auth headers for Server Components ──────────────────────────\n const next = new Headers(req.headers);\n if (result.userId) {\n next.set(HEADER_USER_ID, result.userId);\n next.set(HEADER_ORG_ID, result.orgId ?? \"\");\n next.set(HEADER_ORG_ROLE, result.orgRole ?? \"\");\n next.set(HEADER_SESSION_ID, result.sessionId ?? \"\");\n next.set(HEADER_RISK_LEVEL, result.riskLevel ?? \"low\");\n } else {\n [HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL]\n .forEach((h) => next.delete(h));\n }\n\n return NextResponse.next({ request: { headers: next } });\n };\n}\n\nfunction extractBearer(header: string): string {\n return header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n}\n"],"mappings":"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","names":["createRemoteJWKSet","importSPKI","jwtVerify","importSPKI","jwtVerify","createRemoteJWKSet"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaultix.ai/nextjs",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Vaultix-ID Next.js SDK — edge middleware and server utilities",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"next": ">=14.0.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@vaultix.ai/react": "
|
|
39
|
+
"@vaultix.ai/react": "^0.3.3",
|
|
40
40
|
"jose": "^5.6.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|