@vaultix.ai/nextjs 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +157 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -64
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +13 -15
- package/dist/middleware.d.ts +13 -15
- package/dist/middleware.js +48 -34
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +49 -35
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.d.mts +35 -22
- package/dist/server.d.ts +35 -22
- package/dist/server.js +109 -28
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +111 -30
- package/dist/server.mjs.map +1 -1
- package/package.json +4 -2
package/dist/middleware.js
CHANGED
|
@@ -35,31 +35,51 @@ var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
|
35
35
|
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
36
36
|
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
37
37
|
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
38
|
-
|
|
39
|
-
var cachedPem = null;
|
|
40
|
-
async function getPublicKey() {
|
|
41
|
-
const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;
|
|
42
|
-
if (!pem) return null;
|
|
43
|
-
if (cachedKey && cachedPem === pem) return cachedKey;
|
|
38
|
+
function decodeApiUrlFromKey(pk) {
|
|
44
39
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
const parts = pk.split("_");
|
|
41
|
+
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
42
|
+
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
43
|
+
}
|
|
48
44
|
} catch {
|
|
49
|
-
return null;
|
|
50
45
|
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
function resolveApiUrl(options) {
|
|
49
|
+
if (options.apiUrl) return options.apiUrl.replace(/\/$/, "");
|
|
50
|
+
if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\/$/, "");
|
|
51
|
+
const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? "";
|
|
52
|
+
return decodeApiUrlFromKey(pk);
|
|
53
|
+
}
|
|
54
|
+
var remoteJwks = null;
|
|
55
|
+
var remoteJwksUrl = null;
|
|
56
|
+
var staticKey = null;
|
|
57
|
+
var staticPem = null;
|
|
58
|
+
async function getVerifyKey(apiUrl) {
|
|
59
|
+
const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;
|
|
60
|
+
if (pem) {
|
|
61
|
+
const normalized = pem.replace(/\\n/g, "\n");
|
|
62
|
+
if (staticKey && staticPem === normalized) return { key: staticKey, mode: "static" };
|
|
63
|
+
staticKey = await (0, import_jose.importSPKI)(normalized, "RS256");
|
|
64
|
+
staticPem = normalized;
|
|
65
|
+
return { key: staticKey, mode: "static" };
|
|
66
|
+
}
|
|
67
|
+
if (!apiUrl) return null;
|
|
68
|
+
const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;
|
|
69
|
+
if (!remoteJwks || remoteJwksUrl !== jwksUrl) {
|
|
70
|
+
remoteJwks = (0, import_jose.createRemoteJWKSet)(new URL(jwksUrl));
|
|
71
|
+
remoteJwksUrl = jwksUrl;
|
|
72
|
+
}
|
|
73
|
+
return { key: remoteJwks, mode: "remote" };
|
|
51
74
|
}
|
|
52
75
|
function isPublic(pathname, rules) {
|
|
53
|
-
return rules.some(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
return rule.test(pathname);
|
|
58
|
-
});
|
|
76
|
+
return rules.some(
|
|
77
|
+
(rule) => typeof rule === "string" ? pathname === rule || pathname.startsWith(rule) : rule.test(pathname)
|
|
78
|
+
);
|
|
59
79
|
}
|
|
60
80
|
async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
61
81
|
try {
|
|
62
|
-
const res = await fetch(`${apiUrl}/v1/tokens/exchange`, {
|
|
82
|
+
const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {
|
|
63
83
|
method: "POST",
|
|
64
84
|
headers: { "Content-Type": "application/json" },
|
|
65
85
|
body: JSON.stringify({ handshake_token: handshakeToken })
|
|
@@ -75,7 +95,6 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
75
95
|
sameSite: "lax",
|
|
76
96
|
path: "/",
|
|
77
97
|
maxAge: 30 * 24 * 60 * 60
|
|
78
|
-
// 30 days
|
|
79
98
|
});
|
|
80
99
|
return response;
|
|
81
100
|
} catch {
|
|
@@ -83,21 +102,16 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
83
102
|
}
|
|
84
103
|
}
|
|
85
104
|
function authMiddleware(options = {}) {
|
|
86
|
-
const {
|
|
87
|
-
publicRoutes = [],
|
|
88
|
-
signInUrl = "/sign-in",
|
|
89
|
-
afterAuth
|
|
90
|
-
} = options;
|
|
105
|
+
const { publicRoutes = [], afterAuth } = options;
|
|
91
106
|
return async function middleware(req) {
|
|
92
107
|
const { pathname } = req.nextUrl;
|
|
93
108
|
const publicRoute = isPublic(pathname, publicRoutes);
|
|
109
|
+
const apiUrl = resolveApiUrl(options);
|
|
110
|
+
const signInUrl = options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : "/sign-in");
|
|
94
111
|
const handshakeToken = req.nextUrl.searchParams.get("__vaultix_handshake");
|
|
95
|
-
if (handshakeToken) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
const response = await handleHandshake(req, handshakeToken, apiUrl);
|
|
99
|
-
if (response) return response;
|
|
100
|
-
}
|
|
112
|
+
if (handshakeToken && apiUrl) {
|
|
113
|
+
const response = await handleHandshake(req, handshakeToken, apiUrl);
|
|
114
|
+
if (response) return response;
|
|
101
115
|
}
|
|
102
116
|
let result = {
|
|
103
117
|
userId: null,
|
|
@@ -108,12 +122,12 @@ function authMiddleware(options = {}) {
|
|
|
108
122
|
isSignedIn: false,
|
|
109
123
|
isPublicRoute: publicRoute
|
|
110
124
|
};
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
125
|
+
const token = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
126
|
+
if (token) {
|
|
127
|
+
const verifyKey = await getVerifyKey(apiUrl);
|
|
128
|
+
if (verifyKey) {
|
|
115
129
|
try {
|
|
116
|
-
const { payload } = await (0, import_jose.jwtVerify)(
|
|
130
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, verifyKey.key, {
|
|
117
131
|
algorithms: ["RS256"]
|
|
118
132
|
});
|
|
119
133
|
result = {
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose (not Node crypto) for JWT verification.\nimport { importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n /** Authenticated user ID (`sub` claim), or null if unauthenticated. */\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 are matched by exact path or prefix (e.g. \"/sign-in\" or \"/api/public/\").\n * RegExps are tested against `pathname`.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /** Where to redirect unauthenticated users. Default: \"/sign-in\". */\n signInUrl?: string;\n\n /**\n * Vaultix API origin — required to exchange handshake tokens.\n * Falls back to VAULTIX_API_URL env var.\n * Example: \"https://vaultix.smritix-ai.com\"\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is determined.\n * Return a `NextResponse` to short-circuit the default redirect behaviour;\n * return `undefined` to continue with defaults.\n */\n afterAuth?: (\n auth: AuthResult,\n req: NextRequest,\n ) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names injected into RSC requests ──────────────────────────────────\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// ─── Public-key cache ─────────────────────────────────────────────────────────\n\nlet cachedKey: KeyLike | null = null;\nlet cachedPem: string | null = null;\n\nasync function getPublicKey(): Promise<KeyLike | null> {\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (!pem) return null;\n if (cachedKey && cachedPem === pem) return cachedKey;\n\n try {\n cachedKey = await importSPKI(pem.replace(/\\\\n/g, \"\\n\"), \"RS256\");\n cachedPem = pem;\n return cachedKey;\n } catch {\n return null;\n }\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) => {\n if (typeof rule === \"string\") {\n return pathname === rule || pathname.startsWith(rule);\n }\n return rule.test(pathname);\n });\n}\n\n// ─── Handshake token exchange ─────────────────────────────────────────────────\n// Called when `__vaultix_handshake` appears in the URL.\n// Exchanges the short-lived token for a full session JWT, sets a same-domain\n// cookie, and redirects to the clean URL — mirroring Clerk's __clerk_handshake.\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}/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n\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, // 30 days\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({\n * apiUrl: \"https://vaultix.smritix-ai.com\",\n * publicRoutes: [\"/sign-in\", \"/sign-up\"],\n * });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const {\n publicRoutes = [],\n signInUrl = \"/sign-in\",\n afterAuth,\n } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken) {\n const apiUrl =\n options.apiUrl ??\n process.env.VAULTIX_API_URL ??\n \"\";\n if (apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n // If exchange failed, fall through to normal auth check\n }\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null,\n orgId: null,\n orgRole: null,\n sessionId: null,\n riskLevel: null,\n isSignedIn: false,\n isPublicRoute: publicRoute,\n };\n\n const sessionCookie =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (sessionCookie) {\n const key = await getPublicKey();\n if (key) {\n try {\n const { payload } = await jwtVerify(sessionCookie, key, {\n algorithms: [\"RS256\"],\n });\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 or tampered token → treat as signed-out.\n }\n }\n }\n\n // Let callers override the default redirect logic.\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // Default: redirect unauthenticated requests to the sign-in page.\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 // Propagate auth claims to Server Components via request headers.\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 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":[]}
|
package/dist/middleware.mjs
CHANGED
|
@@ -1,36 +1,56 @@
|
|
|
1
1
|
// src/middleware.ts
|
|
2
|
-
import { importSPKI, jwtVerify } from "jose";
|
|
2
|
+
import { createRemoteJWKSet, importSPKI, jwtVerify } from "jose";
|
|
3
3
|
import { NextResponse } from "next/server";
|
|
4
4
|
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
5
5
|
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
6
6
|
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
7
7
|
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
8
8
|
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
9
|
-
|
|
10
|
-
var cachedPem = null;
|
|
11
|
-
async function getPublicKey() {
|
|
12
|
-
const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;
|
|
13
|
-
if (!pem) return null;
|
|
14
|
-
if (cachedKey && cachedPem === pem) return cachedKey;
|
|
9
|
+
function decodeApiUrlFromKey(pk) {
|
|
15
10
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const parts = pk.split("_");
|
|
12
|
+
if (parts.length >= 4 && parts[0] === "vaultix" && parts[1] === "pk") {
|
|
13
|
+
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
14
|
+
}
|
|
19
15
|
} catch {
|
|
20
|
-
return null;
|
|
21
16
|
}
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
function resolveApiUrl(options) {
|
|
20
|
+
if (options.apiUrl) return options.apiUrl.replace(/\/$/, "");
|
|
21
|
+
if (process.env.VAULTIX_API_URL) return process.env.VAULTIX_API_URL.replace(/\/$/, "");
|
|
22
|
+
const pk = process.env.NEXT_PUBLIC_VAULTIX_PUBLISHABLE_KEY ?? "";
|
|
23
|
+
return decodeApiUrlFromKey(pk);
|
|
24
|
+
}
|
|
25
|
+
var remoteJwks = null;
|
|
26
|
+
var remoteJwksUrl = null;
|
|
27
|
+
var staticKey = null;
|
|
28
|
+
var staticPem = null;
|
|
29
|
+
async function getVerifyKey(apiUrl) {
|
|
30
|
+
const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;
|
|
31
|
+
if (pem) {
|
|
32
|
+
const normalized = pem.replace(/\\n/g, "\n");
|
|
33
|
+
if (staticKey && staticPem === normalized) return { key: staticKey, mode: "static" };
|
|
34
|
+
staticKey = await importSPKI(normalized, "RS256");
|
|
35
|
+
staticPem = normalized;
|
|
36
|
+
return { key: staticKey, mode: "static" };
|
|
37
|
+
}
|
|
38
|
+
if (!apiUrl) return null;
|
|
39
|
+
const jwksUrl = `${apiUrl}/api/v1/.well-known/jwks.json`;
|
|
40
|
+
if (!remoteJwks || remoteJwksUrl !== jwksUrl) {
|
|
41
|
+
remoteJwks = createRemoteJWKSet(new URL(jwksUrl));
|
|
42
|
+
remoteJwksUrl = jwksUrl;
|
|
43
|
+
}
|
|
44
|
+
return { key: remoteJwks, mode: "remote" };
|
|
22
45
|
}
|
|
23
46
|
function isPublic(pathname, rules) {
|
|
24
|
-
return rules.some(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
return rule.test(pathname);
|
|
29
|
-
});
|
|
47
|
+
return rules.some(
|
|
48
|
+
(rule) => typeof rule === "string" ? pathname === rule || pathname.startsWith(rule) : rule.test(pathname)
|
|
49
|
+
);
|
|
30
50
|
}
|
|
31
51
|
async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
32
52
|
try {
|
|
33
|
-
const res = await fetch(`${apiUrl}/v1/tokens/exchange`, {
|
|
53
|
+
const res = await fetch(`${apiUrl}/api/v1/tokens/exchange`, {
|
|
34
54
|
method: "POST",
|
|
35
55
|
headers: { "Content-Type": "application/json" },
|
|
36
56
|
body: JSON.stringify({ handshake_token: handshakeToken })
|
|
@@ -46,7 +66,6 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
46
66
|
sameSite: "lax",
|
|
47
67
|
path: "/",
|
|
48
68
|
maxAge: 30 * 24 * 60 * 60
|
|
49
|
-
// 30 days
|
|
50
69
|
});
|
|
51
70
|
return response;
|
|
52
71
|
} catch {
|
|
@@ -54,21 +73,16 @@ async function handleHandshake(req, handshakeToken, apiUrl) {
|
|
|
54
73
|
}
|
|
55
74
|
}
|
|
56
75
|
function authMiddleware(options = {}) {
|
|
57
|
-
const {
|
|
58
|
-
publicRoutes = [],
|
|
59
|
-
signInUrl = "/sign-in",
|
|
60
|
-
afterAuth
|
|
61
|
-
} = options;
|
|
76
|
+
const { publicRoutes = [], afterAuth } = options;
|
|
62
77
|
return async function middleware(req) {
|
|
63
78
|
const { pathname } = req.nextUrl;
|
|
64
79
|
const publicRoute = isPublic(pathname, publicRoutes);
|
|
80
|
+
const apiUrl = resolveApiUrl(options);
|
|
81
|
+
const signInUrl = options.signInUrl ?? (apiUrl ? `${apiUrl}/auth/sign-in` : "/sign-in");
|
|
65
82
|
const handshakeToken = req.nextUrl.searchParams.get("__vaultix_handshake");
|
|
66
|
-
if (handshakeToken) {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
const response = await handleHandshake(req, handshakeToken, apiUrl);
|
|
70
|
-
if (response) return response;
|
|
71
|
-
}
|
|
83
|
+
if (handshakeToken && apiUrl) {
|
|
84
|
+
const response = await handleHandshake(req, handshakeToken, apiUrl);
|
|
85
|
+
if (response) return response;
|
|
72
86
|
}
|
|
73
87
|
let result = {
|
|
74
88
|
userId: null,
|
|
@@ -79,12 +93,12 @@ function authMiddleware(options = {}) {
|
|
|
79
93
|
isSignedIn: false,
|
|
80
94
|
isPublicRoute: publicRoute
|
|
81
95
|
};
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
96
|
+
const token = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
97
|
+
if (token) {
|
|
98
|
+
const verifyKey = await getVerifyKey(apiUrl);
|
|
99
|
+
if (verifyKey) {
|
|
86
100
|
try {
|
|
87
|
-
const { payload } = await jwtVerify(
|
|
101
|
+
const { payload } = await jwtVerify(token, verifyKey.key, {
|
|
88
102
|
algorithms: ["RS256"]
|
|
89
103
|
});
|
|
90
104
|
result = {
|
package/dist/middleware.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["// Edge-runtime compatible. Uses jose (not Node crypto) for JWT verification.\nimport { importSPKI, jwtVerify, type KeyLike } from \"jose\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AuthResult {\n /** Authenticated user ID (`sub` claim), or null if unauthenticated. */\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 are matched by exact path or prefix (e.g. \"/sign-in\" or \"/api/public/\").\n * RegExps are tested against `pathname`.\n */\n publicRoutes?: Array<string | RegExp>;\n\n /** Where to redirect unauthenticated users. Default: \"/sign-in\". */\n signInUrl?: string;\n\n /**\n * Vaultix API origin — required to exchange handshake tokens.\n * Falls back to VAULTIX_API_URL env var.\n * Example: \"https://vaultix.smritix-ai.com\"\n */\n apiUrl?: string;\n\n /**\n * Custom logic after auth state is determined.\n * Return a `NextResponse` to short-circuit the default redirect behaviour;\n * return `undefined` to continue with defaults.\n */\n afterAuth?: (\n auth: AuthResult,\n req: NextRequest,\n ) => NextResponse | Response | undefined | void;\n}\n\n// ─── Header names injected into RSC requests ──────────────────────────────────\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// ─── Public-key cache ─────────────────────────────────────────────────────────\n\nlet cachedKey: KeyLike | null = null;\nlet cachedPem: string | null = null;\n\nasync function getPublicKey(): Promise<KeyLike | null> {\n const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;\n if (!pem) return null;\n if (cachedKey && cachedPem === pem) return cachedKey;\n\n try {\n cachedKey = await importSPKI(pem.replace(/\\\\n/g, \"\\n\"), \"RS256\");\n cachedPem = pem;\n return cachedKey;\n } catch {\n return null;\n }\n}\n\n// ─── Route matching ───────────────────────────────────────────────────────────\n\nfunction isPublic(pathname: string, rules: Array<string | RegExp>): boolean {\n return rules.some((rule) => {\n if (typeof rule === \"string\") {\n return pathname === rule || pathname.startsWith(rule);\n }\n return rule.test(pathname);\n });\n}\n\n// ─── Handshake token exchange ─────────────────────────────────────────────────\n// Called when `__vaultix_handshake` appears in the URL.\n// Exchanges the short-lived token for a full session JWT, sets a same-domain\n// cookie, and redirects to the clean URL — mirroring Clerk's __clerk_handshake.\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}/v1/tokens/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ handshake_token: handshakeToken }),\n });\n\n if (!res.ok) return null;\n\n const { session_jwt } = (await res.json()) as { session_jwt: string };\n\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, // 30 days\n });\n return response;\n } catch {\n return null;\n }\n}\n\n// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@vaultix.ai/nextjs/middleware\";\n * export default authMiddleware({\n * apiUrl: \"https://vaultix.smritix-ai.com\",\n * publicRoutes: [\"/sign-in\", \"/sign-up\"],\n * });\n * export const config = { matcher: [\"/((?!_next|.*\\\\..*).*)\"] };\n */\nexport function authMiddleware(options: AuthMiddlewareOptions = {}) {\n const {\n publicRoutes = [],\n signInUrl = \"/sign-in\",\n afterAuth,\n } = options;\n\n return async function middleware(req: NextRequest): Promise<NextResponse> {\n const { pathname } = req.nextUrl;\n const publicRoute = isPublic(pathname, publicRoutes);\n\n // ── Handshake exchange ─────────────────────────────────────────────────\n const handshakeToken = req.nextUrl.searchParams.get(\"__vaultix_handshake\");\n if (handshakeToken) {\n const apiUrl =\n options.apiUrl ??\n process.env.VAULTIX_API_URL ??\n \"\";\n if (apiUrl) {\n const response = await handleHandshake(req, handshakeToken, apiUrl);\n if (response) return response;\n // If exchange failed, fall through to normal auth check\n }\n }\n\n // ── JWT verification ───────────────────────────────────────────────────\n let result: AuthResult = {\n userId: null,\n orgId: null,\n orgRole: null,\n sessionId: null,\n riskLevel: null,\n isSignedIn: false,\n isPublicRoute: publicRoute,\n };\n\n const sessionCookie =\n req.cookies.get(\"vaultix-session\")?.value ??\n extractBearer(req.headers.get(\"authorization\") ?? \"\");\n\n if (sessionCookie) {\n const key = await getPublicKey();\n if (key) {\n try {\n const { payload } = await jwtVerify(sessionCookie, key, {\n algorithms: [\"RS256\"],\n });\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 or tampered token → treat as signed-out.\n }\n }\n }\n\n // Let callers override the default redirect logic.\n if (afterAuth) {\n const override = afterAuth(result, req);\n if (override) return override as NextResponse;\n }\n\n // Default: redirect unauthenticated requests to the sign-in page.\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 // Propagate auth claims to Server Components via request headers.\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 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":";AACA,SAAS,oBAAoB,YAAY,iBAA+B;AACxE,SAAsB,oBAAoB;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,MAAM,WAAW,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,iBAAa,mBAAmB,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,aAAa,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,MAAM,UAAU,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,aAAa,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,aAAa,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/server.d.mts
CHANGED
|
@@ -7,45 +7,58 @@ interface AuthObject {
|
|
|
7
7
|
sessionId: string | null;
|
|
8
8
|
riskLevel: "low" | "medium" | "high" | "critical" | null;
|
|
9
9
|
isSignedIn: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Throws a redirect if the user is not authenticated.
|
|
12
|
+
* Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())
|
|
13
|
+
*/
|
|
14
|
+
protect: (redirectTo?: string) => void;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
|
-
* Returns the current auth state
|
|
13
|
-
*
|
|
14
|
-
*
|
|
17
|
+
* Returns the current auth state. Works in Server Components, Route Handlers,
|
|
18
|
+
* and Server Actions. Falls back to verifying the session cookie directly
|
|
19
|
+
* if middleware headers are not present.
|
|
15
20
|
*
|
|
16
21
|
* @example
|
|
17
|
-
* import { auth } from "@
|
|
22
|
+
* import { auth } from "@vaultix.ai/nextjs/server";
|
|
23
|
+
*
|
|
18
24
|
* export default async function Page() {
|
|
19
|
-
* const { userId,
|
|
20
|
-
*
|
|
25
|
+
* const { userId, protect } = await auth();
|
|
26
|
+
* protect(); // redirects to sign-in if not authenticated
|
|
27
|
+
* return <div>Hello {userId}</div>;
|
|
21
28
|
* }
|
|
22
29
|
*/
|
|
23
30
|
declare function auth(): Promise<AuthObject>;
|
|
24
31
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
32
|
+
* Returns the full user record for the currently signed-in user.
|
|
33
|
+
* Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.
|
|
34
|
+
* No extra env vars required.
|
|
27
35
|
*
|
|
28
36
|
* @example
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
37
|
+
* import { currentUser } from "@vaultix.ai/nextjs/server";
|
|
38
|
+
*
|
|
39
|
+
* export default async function Page() {
|
|
40
|
+
* const user = await currentUser();
|
|
41
|
+
* if (!user) redirect("/sign-in");
|
|
42
|
+
* return <div>Hello {user.email}</div>;
|
|
32
43
|
* }
|
|
33
44
|
*/
|
|
34
|
-
declare function
|
|
45
|
+
declare function currentUser(): Promise<VaultixUser | null>;
|
|
35
46
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Returns null when unauthenticated or when env vars are missing.
|
|
39
|
-
*
|
|
40
|
-
* Responses are never cached (`cache: "no-store"`) — user data must be fresh.
|
|
47
|
+
* Returns the active organization for the current user.
|
|
48
|
+
* Requires VAULTIX_SECRET_KEY env var.
|
|
41
49
|
*/
|
|
42
|
-
declare function
|
|
50
|
+
declare function currentOrg(): Promise<VaultixOrganization | null>;
|
|
43
51
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
52
|
+
* Asserts the current user is authenticated. Redirects to sign-in if not.
|
|
53
|
+
* Prefer calling `protect()` from the auth object returned by `auth()`.
|
|
46
54
|
*
|
|
47
|
-
*
|
|
55
|
+
* @example
|
|
56
|
+
* import { protect } from "@vaultix.ai/nextjs/server";
|
|
57
|
+
* export default async function Page() {
|
|
58
|
+
* const { userId } = await protect();
|
|
59
|
+
* return <div>{userId}</div>;
|
|
60
|
+
* }
|
|
48
61
|
*/
|
|
49
|
-
declare function
|
|
62
|
+
declare function protect(redirectTo?: string): Promise<AuthObject>;
|
|
50
63
|
|
|
51
64
|
export { type AuthObject, auth, currentOrg, currentUser, protect };
|
package/dist/server.d.ts
CHANGED
|
@@ -7,45 +7,58 @@ interface AuthObject {
|
|
|
7
7
|
sessionId: string | null;
|
|
8
8
|
riskLevel: "low" | "medium" | "high" | "critical" | null;
|
|
9
9
|
isSignedIn: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Throws a redirect if the user is not authenticated.
|
|
12
|
+
* Usage: const { userId } = await auth(); — or call auth().then(a => a.protect())
|
|
13
|
+
*/
|
|
14
|
+
protect: (redirectTo?: string) => void;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
|
-
* Returns the current auth state
|
|
13
|
-
*
|
|
14
|
-
*
|
|
17
|
+
* Returns the current auth state. Works in Server Components, Route Handlers,
|
|
18
|
+
* and Server Actions. Falls back to verifying the session cookie directly
|
|
19
|
+
* if middleware headers are not present.
|
|
15
20
|
*
|
|
16
21
|
* @example
|
|
17
|
-
* import { auth } from "@
|
|
22
|
+
* import { auth } from "@vaultix.ai/nextjs/server";
|
|
23
|
+
*
|
|
18
24
|
* export default async function Page() {
|
|
19
|
-
* const { userId,
|
|
20
|
-
*
|
|
25
|
+
* const { userId, protect } = await auth();
|
|
26
|
+
* protect(); // redirects to sign-in if not authenticated
|
|
27
|
+
* return <div>Hello {userId}</div>;
|
|
21
28
|
* }
|
|
22
29
|
*/
|
|
23
30
|
declare function auth(): Promise<AuthObject>;
|
|
24
31
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
32
|
+
* Returns the full user record for the currently signed-in user.
|
|
33
|
+
* Calls GET /api/v1/me using the session JWT from the cookie as a Bearer token.
|
|
34
|
+
* No extra env vars required.
|
|
27
35
|
*
|
|
28
36
|
* @example
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
37
|
+
* import { currentUser } from "@vaultix.ai/nextjs/server";
|
|
38
|
+
*
|
|
39
|
+
* export default async function Page() {
|
|
40
|
+
* const user = await currentUser();
|
|
41
|
+
* if (!user) redirect("/sign-in");
|
|
42
|
+
* return <div>Hello {user.email}</div>;
|
|
32
43
|
* }
|
|
33
44
|
*/
|
|
34
|
-
declare function
|
|
45
|
+
declare function currentUser(): Promise<VaultixUser | null>;
|
|
35
46
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Returns null when unauthenticated or when env vars are missing.
|
|
39
|
-
*
|
|
40
|
-
* Responses are never cached (`cache: "no-store"`) — user data must be fresh.
|
|
47
|
+
* Returns the active organization for the current user.
|
|
48
|
+
* Requires VAULTIX_SECRET_KEY env var.
|
|
41
49
|
*/
|
|
42
|
-
declare function
|
|
50
|
+
declare function currentOrg(): Promise<VaultixOrganization | null>;
|
|
43
51
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
52
|
+
* Asserts the current user is authenticated. Redirects to sign-in if not.
|
|
53
|
+
* Prefer calling `protect()` from the auth object returned by `auth()`.
|
|
46
54
|
*
|
|
47
|
-
*
|
|
55
|
+
* @example
|
|
56
|
+
* import { protect } from "@vaultix.ai/nextjs/server";
|
|
57
|
+
* export default async function Page() {
|
|
58
|
+
* const { userId } = await protect();
|
|
59
|
+
* return <div>{userId}</div>;
|
|
60
|
+
* }
|
|
48
61
|
*/
|
|
49
|
-
declare function
|
|
62
|
+
declare function protect(redirectTo?: string): Promise<AuthObject>;
|
|
50
63
|
|
|
51
64
|
export { type AuthObject, auth, currentOrg, currentUser, protect };
|