@smritix.ai/nextjs 0.1.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.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +222 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +192 -0
- package/dist/index.mjs.map +1 -0
- package/dist/middleware.d.mts +45 -0
- package/dist/middleware.d.ts +45 -0
- package/dist/middleware.js +133 -0
- package/dist/middleware.js.map +1 -0
- package/dist/middleware.mjs +103 -0
- package/dist/middleware.mjs.map +1 -0
- package/dist/server.d.mts +51 -0
- package/dist/server.d.ts +51 -0
- package/dist/server.js +113 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +85 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +48 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AuthMiddlewareOptions, AuthResult, authMiddleware } from './middleware.mjs';
|
|
2
|
+
export { AuthObject, auth, currentOrg, currentUser, protect } from './server.mjs';
|
|
3
|
+
export { ChallengeType, OrganizationSwitcher, RiskLevel, SessionClaims, SignIn, SignUp, UserButton, VaultixContextValue, VaultixOrganization, VaultixProvider, VaultixProviderProps, VaultixSession, VaultixUser, useOrganization, useSession, useUser, useVaultix } from '@smritix.ai/react';
|
|
4
|
+
import 'next/server';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AuthMiddlewareOptions, AuthResult, authMiddleware } from './middleware.js';
|
|
2
|
+
export { AuthObject, auth, currentOrg, currentUser, protect } from './server.js';
|
|
3
|
+
export { ChallengeType, OrganizationSwitcher, RiskLevel, SessionClaims, SignIn, SignUp, UserButton, VaultixContextValue, VaultixOrganization, VaultixProvider, VaultixProviderProps, VaultixSession, VaultixUser, useOrganization, useSession, useUser, useVaultix } from '@smritix.ai/react';
|
|
4
|
+
import 'next/server';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
OrganizationSwitcher: () => import_react.OrganizationSwitcher,
|
|
24
|
+
SignIn: () => import_react.SignIn,
|
|
25
|
+
SignUp: () => import_react.SignUp,
|
|
26
|
+
UserButton: () => import_react.UserButton,
|
|
27
|
+
VaultixProvider: () => import_react.VaultixProvider,
|
|
28
|
+
auth: () => auth,
|
|
29
|
+
authMiddleware: () => authMiddleware,
|
|
30
|
+
currentOrg: () => currentOrg,
|
|
31
|
+
currentUser: () => currentUser,
|
|
32
|
+
protect: () => protect,
|
|
33
|
+
useOrganization: () => import_react.useOrganization,
|
|
34
|
+
useSession: () => import_react.useSession,
|
|
35
|
+
useUser: () => import_react.useUser,
|
|
36
|
+
useVaultix: () => import_react.useVaultix
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
39
|
+
|
|
40
|
+
// src/middleware.ts
|
|
41
|
+
var import_jose = require("jose");
|
|
42
|
+
var import_server = require("next/server");
|
|
43
|
+
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
44
|
+
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
45
|
+
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
46
|
+
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
47
|
+
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
48
|
+
var cachedKey = null;
|
|
49
|
+
var cachedPem = null;
|
|
50
|
+
async function getPublicKey() {
|
|
51
|
+
const pem = process.env.VAULTIX_JWT_PUBLIC_KEY;
|
|
52
|
+
if (!pem) return null;
|
|
53
|
+
if (cachedKey && cachedPem === pem) return cachedKey;
|
|
54
|
+
try {
|
|
55
|
+
cachedKey = await (0, import_jose.importSPKI)(pem, "RS256");
|
|
56
|
+
cachedPem = pem;
|
|
57
|
+
return cachedKey;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isPublic(pathname, rules) {
|
|
63
|
+
return rules.some((rule) => {
|
|
64
|
+
if (typeof rule === "string") {
|
|
65
|
+
return pathname === rule || pathname.startsWith(rule);
|
|
66
|
+
}
|
|
67
|
+
return rule.test(pathname);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function authMiddleware(options = {}) {
|
|
71
|
+
const {
|
|
72
|
+
publicRoutes = [],
|
|
73
|
+
signInUrl = "/sign-in",
|
|
74
|
+
afterAuth
|
|
75
|
+
} = options;
|
|
76
|
+
return async function middleware(req) {
|
|
77
|
+
const { pathname } = req.nextUrl;
|
|
78
|
+
const publicRoute = isPublic(pathname, publicRoutes);
|
|
79
|
+
let result = {
|
|
80
|
+
userId: null,
|
|
81
|
+
orgId: null,
|
|
82
|
+
orgRole: null,
|
|
83
|
+
sessionId: null,
|
|
84
|
+
riskLevel: null,
|
|
85
|
+
isSignedIn: false,
|
|
86
|
+
isPublicRoute: publicRoute
|
|
87
|
+
};
|
|
88
|
+
const sessionCookie = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
89
|
+
if (sessionCookie) {
|
|
90
|
+
const key = await getPublicKey();
|
|
91
|
+
if (key) {
|
|
92
|
+
try {
|
|
93
|
+
const { payload } = await (0, import_jose.jwtVerify)(sessionCookie, key, {
|
|
94
|
+
algorithms: ["RS256"]
|
|
95
|
+
});
|
|
96
|
+
result = {
|
|
97
|
+
userId: payload["uid"] ?? null,
|
|
98
|
+
orgId: payload["org"] ?? null,
|
|
99
|
+
orgRole: payload["rol"] ?? null,
|
|
100
|
+
sessionId: payload["sid"] ?? null,
|
|
101
|
+
riskLevel: payload["risk"] ?? "low",
|
|
102
|
+
isSignedIn: true,
|
|
103
|
+
isPublicRoute: publicRoute
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (afterAuth) {
|
|
110
|
+
const override = afterAuth(result, req);
|
|
111
|
+
if (override) return override;
|
|
112
|
+
}
|
|
113
|
+
if (!result.isSignedIn && !publicRoute) {
|
|
114
|
+
const dest = new URL(signInUrl, req.url);
|
|
115
|
+
dest.searchParams.set("redirect_url", req.url);
|
|
116
|
+
return import_server.NextResponse.redirect(dest);
|
|
117
|
+
}
|
|
118
|
+
const next = new Headers(req.headers);
|
|
119
|
+
if (result.userId) {
|
|
120
|
+
next.set(HEADER_USER_ID, result.userId);
|
|
121
|
+
next.set(HEADER_ORG_ID, result.orgId ?? "");
|
|
122
|
+
next.set(HEADER_ORG_ROLE, result.orgRole ?? "");
|
|
123
|
+
next.set(HEADER_SESSION_ID, result.sessionId ?? "");
|
|
124
|
+
next.set(HEADER_RISK_LEVEL, result.riskLevel ?? "low");
|
|
125
|
+
} else {
|
|
126
|
+
[HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL].forEach((h) => next.delete(h));
|
|
127
|
+
}
|
|
128
|
+
return import_server.NextResponse.next({ request: { headers: next } });
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function extractBearer(header) {
|
|
132
|
+
return header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/server.ts
|
|
136
|
+
var import_headers = require("next/headers");
|
|
137
|
+
var import_navigation = require("next/navigation");
|
|
138
|
+
async function auth() {
|
|
139
|
+
const h = await (0, import_headers.headers)();
|
|
140
|
+
const userId = h.get(HEADER_USER_ID);
|
|
141
|
+
if (!userId) {
|
|
142
|
+
return {
|
|
143
|
+
userId: null,
|
|
144
|
+
orgId: null,
|
|
145
|
+
orgRole: null,
|
|
146
|
+
sessionId: null,
|
|
147
|
+
riskLevel: null,
|
|
148
|
+
isSignedIn: false
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
userId,
|
|
153
|
+
orgId: h.get(HEADER_ORG_ID) || null,
|
|
154
|
+
orgRole: h.get(HEADER_ORG_ROLE) || null,
|
|
155
|
+
sessionId: h.get(HEADER_SESSION_ID) || null,
|
|
156
|
+
riskLevel: h.get(HEADER_RISK_LEVEL) ?? "low",
|
|
157
|
+
isSignedIn: true
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function protect(redirectTo = "/sign-in") {
|
|
161
|
+
const authObj = await auth();
|
|
162
|
+
if (!authObj.isSignedIn) {
|
|
163
|
+
(0, import_navigation.redirect)(redirectTo);
|
|
164
|
+
}
|
|
165
|
+
return authObj;
|
|
166
|
+
}
|
|
167
|
+
async function currentUser() {
|
|
168
|
+
const { userId } = await auth();
|
|
169
|
+
if (!userId) return null;
|
|
170
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
171
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
172
|
+
if (!apiUrl || !secret) return null;
|
|
173
|
+
try {
|
|
174
|
+
const res = await fetch(`${apiUrl}/v1/users/${userId}`, {
|
|
175
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
176
|
+
cache: "no-store"
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok) return null;
|
|
179
|
+
return await res.json();
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function currentOrg() {
|
|
185
|
+
const { orgId } = await auth();
|
|
186
|
+
if (!orgId) return null;
|
|
187
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
188
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
189
|
+
if (!apiUrl || !secret) return null;
|
|
190
|
+
try {
|
|
191
|
+
const orgFetchInit = {
|
|
192
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
193
|
+
next: { revalidate: 30 }
|
|
194
|
+
};
|
|
195
|
+
const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit);
|
|
196
|
+
if (!res.ok) return null;
|
|
197
|
+
return await res.json();
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/index.ts
|
|
204
|
+
var import_react = require("@smritix.ai/react");
|
|
205
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
206
|
+
0 && (module.exports = {
|
|
207
|
+
OrganizationSwitcher,
|
|
208
|
+
SignIn,
|
|
209
|
+
SignUp,
|
|
210
|
+
UserButton,
|
|
211
|
+
VaultixProvider,
|
|
212
|
+
auth,
|
|
213
|
+
authMiddleware,
|
|
214
|
+
currentOrg,
|
|
215
|
+
currentUser,
|
|
216
|
+
protect,
|
|
217
|
+
useOrganization,
|
|
218
|
+
useSession,
|
|
219
|
+
useUser,
|
|
220
|
+
useVaultix
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 \"@smritix.ai/react\";\n\nexport type {\n ChallengeType,\n RiskLevel,\n SessionClaims,\n VaultixContextValue,\n VaultixOrganization,\n VaultixProviderProps,\n VaultixSession,\n VaultixUser,\n} from \"@smritix.ai/react\";\n","// 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 * 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// Importing an RSA public key is CPU-bound work; cache it across invocations.\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, \"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// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@smritix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/sign-in\", \"/sign-up\"] });\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 // Attempt to verify the session cookie.\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 // Clear any previously-set headers (defence-in-depth).\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","// This file is server-only. Import from \"@smritix.ai/nextjs/server\".\n// Do NOT import in Client Components — it will throw at runtime.\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport type { VaultixOrganization, VaultixUser } from \"@smritix.ai/react\";\nimport {\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_RISK_LEVEL,\n HEADER_SESSION_ID,\n HEADER_USER_ID,\n} from \"./middleware\";\n\n// Next.js extends RequestInit with cache and next options for its data cache.\ntype NextRequestInit = RequestInit & {\n cache?: RequestCache;\n next?: { revalidate?: number | false; tags?: string[] };\n};\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\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state by reading the headers injected by\n * `authMiddleware`. Call this in Server Components, Route Handlers, and\n * Server Actions — never in Client Components.\n *\n * @example\n * import { auth } from \"@smritix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId, orgId } = await auth();\n * …\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n\n if (!userId) {\n return {\n userId: null,\n orgId: null,\n orgRole: null,\n sessionId: null,\n riskLevel: null,\n isSignedIn: false,\n };\n }\n\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 };\n}\n\n// ─── protect() ────────────────────────────────────────────────────────────────\n\n/**\n * Asserts that the current user is authenticated. Redirects to `/sign-in`\n * if not. Returns the auth object for convenience.\n *\n * @example\n * export default async function ProtectedPage() {\n * const { userId } = await protect();\n * …\n * }\n */\nexport async function protect(redirectTo = \"/sign-in\"): Promise<AuthObject> {\n const authObj = await auth();\n if (!authObj.isSignedIn) {\n redirect(redirectTo);\n }\n return authObj;\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the full `VaultixUser` record from the auth engine using server-to-server\n * credentials (`VAULTIX_API_URL` + `VAULTIX_SECRET_KEY` env vars).\n * Returns null when unauthenticated or when env vars are missing.\n *\n * Responses are never cached (`cache: \"no-store\"`) — user data must be fresh.\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const { userId } = await auth();\n if (!userId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/v1/users/${userId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n return (await res.json()) as VaultixUser;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the active `VaultixOrganization` from the auth engine.\n * Returns null when the user has no active org or env vars are missing.\n *\n * Responses are cached for 30 seconds — org data changes infrequently.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const orgFetchInit: NextRequestInit = {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n };\n const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// src/middleware.ts
|
|
2
|
+
import { importSPKI, jwtVerify } from "jose";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
5
|
+
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
6
|
+
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
7
|
+
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
8
|
+
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
9
|
+
var cachedKey = null;
|
|
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;
|
|
15
|
+
try {
|
|
16
|
+
cachedKey = await importSPKI(pem, "RS256");
|
|
17
|
+
cachedPem = pem;
|
|
18
|
+
return cachedKey;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isPublic(pathname, rules) {
|
|
24
|
+
return rules.some((rule) => {
|
|
25
|
+
if (typeof rule === "string") {
|
|
26
|
+
return pathname === rule || pathname.startsWith(rule);
|
|
27
|
+
}
|
|
28
|
+
return rule.test(pathname);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function authMiddleware(options = {}) {
|
|
32
|
+
const {
|
|
33
|
+
publicRoutes = [],
|
|
34
|
+
signInUrl = "/sign-in",
|
|
35
|
+
afterAuth
|
|
36
|
+
} = options;
|
|
37
|
+
return async function middleware(req) {
|
|
38
|
+
const { pathname } = req.nextUrl;
|
|
39
|
+
const publicRoute = isPublic(pathname, publicRoutes);
|
|
40
|
+
let result = {
|
|
41
|
+
userId: null,
|
|
42
|
+
orgId: null,
|
|
43
|
+
orgRole: null,
|
|
44
|
+
sessionId: null,
|
|
45
|
+
riskLevel: null,
|
|
46
|
+
isSignedIn: false,
|
|
47
|
+
isPublicRoute: publicRoute
|
|
48
|
+
};
|
|
49
|
+
const sessionCookie = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
50
|
+
if (sessionCookie) {
|
|
51
|
+
const key = await getPublicKey();
|
|
52
|
+
if (key) {
|
|
53
|
+
try {
|
|
54
|
+
const { payload } = await jwtVerify(sessionCookie, key, {
|
|
55
|
+
algorithms: ["RS256"]
|
|
56
|
+
});
|
|
57
|
+
result = {
|
|
58
|
+
userId: payload["uid"] ?? null,
|
|
59
|
+
orgId: payload["org"] ?? null,
|
|
60
|
+
orgRole: payload["rol"] ?? null,
|
|
61
|
+
sessionId: payload["sid"] ?? null,
|
|
62
|
+
riskLevel: payload["risk"] ?? "low",
|
|
63
|
+
isSignedIn: true,
|
|
64
|
+
isPublicRoute: publicRoute
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (afterAuth) {
|
|
71
|
+
const override = afterAuth(result, req);
|
|
72
|
+
if (override) return override;
|
|
73
|
+
}
|
|
74
|
+
if (!result.isSignedIn && !publicRoute) {
|
|
75
|
+
const dest = new URL(signInUrl, req.url);
|
|
76
|
+
dest.searchParams.set("redirect_url", req.url);
|
|
77
|
+
return NextResponse.redirect(dest);
|
|
78
|
+
}
|
|
79
|
+
const next = new Headers(req.headers);
|
|
80
|
+
if (result.userId) {
|
|
81
|
+
next.set(HEADER_USER_ID, result.userId);
|
|
82
|
+
next.set(HEADER_ORG_ID, result.orgId ?? "");
|
|
83
|
+
next.set(HEADER_ORG_ROLE, result.orgRole ?? "");
|
|
84
|
+
next.set(HEADER_SESSION_ID, result.sessionId ?? "");
|
|
85
|
+
next.set(HEADER_RISK_LEVEL, result.riskLevel ?? "low");
|
|
86
|
+
} else {
|
|
87
|
+
[HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL].forEach((h) => next.delete(h));
|
|
88
|
+
}
|
|
89
|
+
return NextResponse.next({ request: { headers: next } });
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function extractBearer(header) {
|
|
93
|
+
return header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/server.ts
|
|
97
|
+
import { headers } from "next/headers";
|
|
98
|
+
import { redirect } from "next/navigation";
|
|
99
|
+
async function auth() {
|
|
100
|
+
const h = await headers();
|
|
101
|
+
const userId = h.get(HEADER_USER_ID);
|
|
102
|
+
if (!userId) {
|
|
103
|
+
return {
|
|
104
|
+
userId: null,
|
|
105
|
+
orgId: null,
|
|
106
|
+
orgRole: null,
|
|
107
|
+
sessionId: null,
|
|
108
|
+
riskLevel: null,
|
|
109
|
+
isSignedIn: false
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
userId,
|
|
114
|
+
orgId: h.get(HEADER_ORG_ID) || null,
|
|
115
|
+
orgRole: h.get(HEADER_ORG_ROLE) || null,
|
|
116
|
+
sessionId: h.get(HEADER_SESSION_ID) || null,
|
|
117
|
+
riskLevel: h.get(HEADER_RISK_LEVEL) ?? "low",
|
|
118
|
+
isSignedIn: true
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function protect(redirectTo = "/sign-in") {
|
|
122
|
+
const authObj = await auth();
|
|
123
|
+
if (!authObj.isSignedIn) {
|
|
124
|
+
redirect(redirectTo);
|
|
125
|
+
}
|
|
126
|
+
return authObj;
|
|
127
|
+
}
|
|
128
|
+
async function currentUser() {
|
|
129
|
+
const { userId } = await auth();
|
|
130
|
+
if (!userId) return null;
|
|
131
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
132
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
133
|
+
if (!apiUrl || !secret) return null;
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch(`${apiUrl}/v1/users/${userId}`, {
|
|
136
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
137
|
+
cache: "no-store"
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) return null;
|
|
140
|
+
return await res.json();
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function currentOrg() {
|
|
146
|
+
const { orgId } = await auth();
|
|
147
|
+
if (!orgId) return null;
|
|
148
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
149
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
150
|
+
if (!apiUrl || !secret) return null;
|
|
151
|
+
try {
|
|
152
|
+
const orgFetchInit = {
|
|
153
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
154
|
+
next: { revalidate: 30 }
|
|
155
|
+
};
|
|
156
|
+
const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit);
|
|
157
|
+
if (!res.ok) return null;
|
|
158
|
+
return await res.json();
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/index.ts
|
|
165
|
+
import {
|
|
166
|
+
OrganizationSwitcher,
|
|
167
|
+
SignIn,
|
|
168
|
+
SignUp,
|
|
169
|
+
VaultixProvider,
|
|
170
|
+
UserButton,
|
|
171
|
+
useOrganization,
|
|
172
|
+
useSession,
|
|
173
|
+
useVaultix,
|
|
174
|
+
useUser
|
|
175
|
+
} from "@smritix.ai/react";
|
|
176
|
+
export {
|
|
177
|
+
OrganizationSwitcher,
|
|
178
|
+
SignIn,
|
|
179
|
+
SignUp,
|
|
180
|
+
UserButton,
|
|
181
|
+
VaultixProvider,
|
|
182
|
+
auth,
|
|
183
|
+
authMiddleware,
|
|
184
|
+
currentOrg,
|
|
185
|
+
currentUser,
|
|
186
|
+
protect,
|
|
187
|
+
useOrganization,
|
|
188
|
+
useSession,
|
|
189
|
+
useUser,
|
|
190
|
+
useVaultix
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/server.ts","../src/index.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 * 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// Importing an RSA public key is CPU-bound work; cache it across invocations.\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, \"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// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@smritix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/sign-in\", \"/sign-up\"] });\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 // Attempt to verify the session cookie.\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 // Clear any previously-set headers (defence-in-depth).\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","// This file is server-only. Import from \"@smritix.ai/nextjs/server\".\n// Do NOT import in Client Components — it will throw at runtime.\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport type { VaultixOrganization, VaultixUser } from \"@smritix.ai/react\";\nimport {\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_RISK_LEVEL,\n HEADER_SESSION_ID,\n HEADER_USER_ID,\n} from \"./middleware\";\n\n// Next.js extends RequestInit with cache and next options for its data cache.\ntype NextRequestInit = RequestInit & {\n cache?: RequestCache;\n next?: { revalidate?: number | false; tags?: string[] };\n};\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\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state by reading the headers injected by\n * `authMiddleware`. Call this in Server Components, Route Handlers, and\n * Server Actions — never in Client Components.\n *\n * @example\n * import { auth } from \"@smritix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId, orgId } = await auth();\n * …\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n\n if (!userId) {\n return {\n userId: null,\n orgId: null,\n orgRole: null,\n sessionId: null,\n riskLevel: null,\n isSignedIn: false,\n };\n }\n\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 };\n}\n\n// ─── protect() ────────────────────────────────────────────────────────────────\n\n/**\n * Asserts that the current user is authenticated. Redirects to `/sign-in`\n * if not. Returns the auth object for convenience.\n *\n * @example\n * export default async function ProtectedPage() {\n * const { userId } = await protect();\n * …\n * }\n */\nexport async function protect(redirectTo = \"/sign-in\"): Promise<AuthObject> {\n const authObj = await auth();\n if (!authObj.isSignedIn) {\n redirect(redirectTo);\n }\n return authObj;\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the full `VaultixUser` record from the auth engine using server-to-server\n * credentials (`VAULTIX_API_URL` + `VAULTIX_SECRET_KEY` env vars).\n * Returns null when unauthenticated or when env vars are missing.\n *\n * Responses are never cached (`cache: \"no-store\"`) — user data must be fresh.\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const { userId } = await auth();\n if (!userId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/v1/users/${userId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n return (await res.json()) as VaultixUser;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the active `VaultixOrganization` from the auth engine.\n * Returns null when the user has no active org or env vars are missing.\n *\n * Responses are cached for 30 seconds — org data changes infrequently.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const orgFetchInit: NextRequestInit = {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n };\n const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\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 \"@smritix.ai/react\";\n\nexport type {\n ChallengeType,\n RiskLevel,\n SessionClaims,\n VaultixContextValue,\n VaultixOrganization,\n VaultixProviderProps,\n VaultixSession,\n VaultixUser,\n} from \"@smritix.ai/react\";\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface AuthResult {
|
|
4
|
+
/** Authenticated user ID (`sub` claim), or null if unauthenticated. */
|
|
5
|
+
userId: string | null;
|
|
6
|
+
orgId: string | null;
|
|
7
|
+
orgRole: string | null;
|
|
8
|
+
sessionId: string | null;
|
|
9
|
+
riskLevel: string | null;
|
|
10
|
+
isSignedIn: boolean;
|
|
11
|
+
isPublicRoute: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface AuthMiddlewareOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Routes that do not require authentication.
|
|
16
|
+
* Strings are matched by exact path or prefix (e.g. "/sign-in" or "/api/public/").
|
|
17
|
+
* RegExps are tested against `pathname`.
|
|
18
|
+
*/
|
|
19
|
+
publicRoutes?: Array<string | RegExp>;
|
|
20
|
+
/** Where to redirect unauthenticated users. Default: "/sign-in". */
|
|
21
|
+
signInUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Custom logic after auth state is determined.
|
|
24
|
+
* Return a `NextResponse` to short-circuit the default redirect behaviour;
|
|
25
|
+
* return `undefined` to continue with defaults.
|
|
26
|
+
*/
|
|
27
|
+
afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;
|
|
28
|
+
}
|
|
29
|
+
declare const HEADER_USER_ID = "x-vaultix-user-id";
|
|
30
|
+
declare const HEADER_ORG_ID = "x-vaultix-org-id";
|
|
31
|
+
declare const HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
32
|
+
declare const HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
33
|
+
declare const HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
34
|
+
/**
|
|
35
|
+
* authMiddleware wraps your Next.js middleware.ts export.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // middleware.ts
|
|
39
|
+
* import { authMiddleware } from "@smritix.ai/nextjs/middleware";
|
|
40
|
+
* export default authMiddleware({ publicRoutes: ["/sign-in", "/sign-up"] });
|
|
41
|
+
* export const config = { matcher: ["/((?!_next|.*\\..*).*)"] };
|
|
42
|
+
*/
|
|
43
|
+
declare function authMiddleware(options?: AuthMiddlewareOptions): (req: NextRequest) => Promise<NextResponse>;
|
|
44
|
+
|
|
45
|
+
export { type AuthMiddlewareOptions, type AuthResult, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_RISK_LEVEL, HEADER_SESSION_ID, HEADER_USER_ID, authMiddleware };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface AuthResult {
|
|
4
|
+
/** Authenticated user ID (`sub` claim), or null if unauthenticated. */
|
|
5
|
+
userId: string | null;
|
|
6
|
+
orgId: string | null;
|
|
7
|
+
orgRole: string | null;
|
|
8
|
+
sessionId: string | null;
|
|
9
|
+
riskLevel: string | null;
|
|
10
|
+
isSignedIn: boolean;
|
|
11
|
+
isPublicRoute: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface AuthMiddlewareOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Routes that do not require authentication.
|
|
16
|
+
* Strings are matched by exact path or prefix (e.g. "/sign-in" or "/api/public/").
|
|
17
|
+
* RegExps are tested against `pathname`.
|
|
18
|
+
*/
|
|
19
|
+
publicRoutes?: Array<string | RegExp>;
|
|
20
|
+
/** Where to redirect unauthenticated users. Default: "/sign-in". */
|
|
21
|
+
signInUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Custom logic after auth state is determined.
|
|
24
|
+
* Return a `NextResponse` to short-circuit the default redirect behaviour;
|
|
25
|
+
* return `undefined` to continue with defaults.
|
|
26
|
+
*/
|
|
27
|
+
afterAuth?: (auth: AuthResult, req: NextRequest) => NextResponse | Response | undefined | void;
|
|
28
|
+
}
|
|
29
|
+
declare const HEADER_USER_ID = "x-vaultix-user-id";
|
|
30
|
+
declare const HEADER_ORG_ID = "x-vaultix-org-id";
|
|
31
|
+
declare const HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
32
|
+
declare const HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
33
|
+
declare const HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
34
|
+
/**
|
|
35
|
+
* authMiddleware wraps your Next.js middleware.ts export.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // middleware.ts
|
|
39
|
+
* import { authMiddleware } from "@smritix.ai/nextjs/middleware";
|
|
40
|
+
* export default authMiddleware({ publicRoutes: ["/sign-in", "/sign-up"] });
|
|
41
|
+
* export const config = { matcher: ["/((?!_next|.*\\..*).*)"] };
|
|
42
|
+
*/
|
|
43
|
+
declare function authMiddleware(options?: AuthMiddlewareOptions): (req: NextRequest) => Promise<NextResponse>;
|
|
44
|
+
|
|
45
|
+
export { type AuthMiddlewareOptions, type AuthResult, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_RISK_LEVEL, HEADER_SESSION_ID, HEADER_USER_ID, authMiddleware };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware.ts
|
|
21
|
+
var middleware_exports = {};
|
|
22
|
+
__export(middleware_exports, {
|
|
23
|
+
HEADER_ORG_ID: () => HEADER_ORG_ID,
|
|
24
|
+
HEADER_ORG_ROLE: () => HEADER_ORG_ROLE,
|
|
25
|
+
HEADER_RISK_LEVEL: () => HEADER_RISK_LEVEL,
|
|
26
|
+
HEADER_SESSION_ID: () => HEADER_SESSION_ID,
|
|
27
|
+
HEADER_USER_ID: () => HEADER_USER_ID,
|
|
28
|
+
authMiddleware: () => authMiddleware
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(middleware_exports);
|
|
31
|
+
var import_jose = require("jose");
|
|
32
|
+
var import_server = require("next/server");
|
|
33
|
+
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
34
|
+
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
35
|
+
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
36
|
+
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
37
|
+
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
38
|
+
var cachedKey = null;
|
|
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;
|
|
44
|
+
try {
|
|
45
|
+
cachedKey = await (0, import_jose.importSPKI)(pem, "RS256");
|
|
46
|
+
cachedPem = pem;
|
|
47
|
+
return cachedKey;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isPublic(pathname, rules) {
|
|
53
|
+
return rules.some((rule) => {
|
|
54
|
+
if (typeof rule === "string") {
|
|
55
|
+
return pathname === rule || pathname.startsWith(rule);
|
|
56
|
+
}
|
|
57
|
+
return rule.test(pathname);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function authMiddleware(options = {}) {
|
|
61
|
+
const {
|
|
62
|
+
publicRoutes = [],
|
|
63
|
+
signInUrl = "/sign-in",
|
|
64
|
+
afterAuth
|
|
65
|
+
} = options;
|
|
66
|
+
return async function middleware(req) {
|
|
67
|
+
const { pathname } = req.nextUrl;
|
|
68
|
+
const publicRoute = isPublic(pathname, publicRoutes);
|
|
69
|
+
let result = {
|
|
70
|
+
userId: null,
|
|
71
|
+
orgId: null,
|
|
72
|
+
orgRole: null,
|
|
73
|
+
sessionId: null,
|
|
74
|
+
riskLevel: null,
|
|
75
|
+
isSignedIn: false,
|
|
76
|
+
isPublicRoute: publicRoute
|
|
77
|
+
};
|
|
78
|
+
const sessionCookie = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
79
|
+
if (sessionCookie) {
|
|
80
|
+
const key = await getPublicKey();
|
|
81
|
+
if (key) {
|
|
82
|
+
try {
|
|
83
|
+
const { payload } = await (0, import_jose.jwtVerify)(sessionCookie, key, {
|
|
84
|
+
algorithms: ["RS256"]
|
|
85
|
+
});
|
|
86
|
+
result = {
|
|
87
|
+
userId: payload["uid"] ?? null,
|
|
88
|
+
orgId: payload["org"] ?? null,
|
|
89
|
+
orgRole: payload["rol"] ?? null,
|
|
90
|
+
sessionId: payload["sid"] ?? null,
|
|
91
|
+
riskLevel: payload["risk"] ?? "low",
|
|
92
|
+
isSignedIn: true,
|
|
93
|
+
isPublicRoute: publicRoute
|
|
94
|
+
};
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (afterAuth) {
|
|
100
|
+
const override = afterAuth(result, req);
|
|
101
|
+
if (override) return override;
|
|
102
|
+
}
|
|
103
|
+
if (!result.isSignedIn && !publicRoute) {
|
|
104
|
+
const dest = new URL(signInUrl, req.url);
|
|
105
|
+
dest.searchParams.set("redirect_url", req.url);
|
|
106
|
+
return import_server.NextResponse.redirect(dest);
|
|
107
|
+
}
|
|
108
|
+
const next = new Headers(req.headers);
|
|
109
|
+
if (result.userId) {
|
|
110
|
+
next.set(HEADER_USER_ID, result.userId);
|
|
111
|
+
next.set(HEADER_ORG_ID, result.orgId ?? "");
|
|
112
|
+
next.set(HEADER_ORG_ROLE, result.orgRole ?? "");
|
|
113
|
+
next.set(HEADER_SESSION_ID, result.sessionId ?? "");
|
|
114
|
+
next.set(HEADER_RISK_LEVEL, result.riskLevel ?? "low");
|
|
115
|
+
} else {
|
|
116
|
+
[HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL].forEach((h) => next.delete(h));
|
|
117
|
+
}
|
|
118
|
+
return import_server.NextResponse.next({ request: { headers: next } });
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function extractBearer(header) {
|
|
122
|
+
return header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
123
|
+
}
|
|
124
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
125
|
+
0 && (module.exports = {
|
|
126
|
+
HEADER_ORG_ID,
|
|
127
|
+
HEADER_ORG_ROLE,
|
|
128
|
+
HEADER_RISK_LEVEL,
|
|
129
|
+
HEADER_SESSION_ID,
|
|
130
|
+
HEADER_USER_ID,
|
|
131
|
+
authMiddleware
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +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 * 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// Importing an RSA public key is CPU-bound work; cache it across invocations.\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, \"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// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@smritix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/sign-in\", \"/sign-up\"] });\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 // Attempt to verify the session cookie.\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 // Clear any previously-set headers (defence-in-depth).\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,kBAAoD;AACpD,oBAA0C;AAuCnC,IAAM,iBAAmB;AACzB,IAAM,gBAAmB;AACzB,IAAM,kBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAKjC,IAAI,YAA4B;AAChC,IAAI,YAA2B;AAE/B,eAAe,eAAwC;AACrD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,aAAa,cAAc,IAAK,QAAO;AAE3C,MAAI;AACF,gBAAY,UAAM,wBAAW,KAAK,OAAO;AACzC,gBAAY;AACZ,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,SAAS,UAAkB,OAAwC;AAC1E,SAAO,MAAM,KAAK,CAAC,SAAS;AAC1B,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO,aAAa,QAAQ,SAAS,WAAW,IAAI;AAAA,IACtD;AACA,WAAO,KAAK,KAAK,QAAQ;AAAA,EAC3B,CAAC;AACH;AAaO,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,QAAM;AAAA,IACJ,eAAe,CAAC;AAAA,IAChB,YAAY;AAAA,IACZ;AAAA,EACF,IAAI;AAEJ,SAAO,eAAe,WAAW,KAAyC;AACxE,UAAM,EAAE,SAAS,IAAI,IAAI;AACzB,UAAM,cAAc,SAAS,UAAU,YAAY;AAGnD,QAAI,SAAqB;AAAA,MACvB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,eAAe;AAAA,IACjB;AAEA,UAAM,gBACJ,IAAI,QAAQ,IAAI,iBAAiB,GAAG,SACpC,cAAc,IAAI,QAAQ,IAAI,eAAe,KAAK,EAAE;AAEtD,QAAI,eAAe;AACjB,YAAM,MAAM,MAAM,aAAa;AAC/B,UAAI,KAAK;AACP,YAAI;AACF,gBAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,eAAe,KAAK;AAAA,YACtD,YAAY,CAAC,OAAO;AAAA,UACtB,CAAC;AAED,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;AAEL,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":[]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// src/middleware.ts
|
|
2
|
+
import { importSPKI, jwtVerify } from "jose";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
5
|
+
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
6
|
+
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
7
|
+
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
8
|
+
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
9
|
+
var cachedKey = null;
|
|
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;
|
|
15
|
+
try {
|
|
16
|
+
cachedKey = await importSPKI(pem, "RS256");
|
|
17
|
+
cachedPem = pem;
|
|
18
|
+
return cachedKey;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isPublic(pathname, rules) {
|
|
24
|
+
return rules.some((rule) => {
|
|
25
|
+
if (typeof rule === "string") {
|
|
26
|
+
return pathname === rule || pathname.startsWith(rule);
|
|
27
|
+
}
|
|
28
|
+
return rule.test(pathname);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function authMiddleware(options = {}) {
|
|
32
|
+
const {
|
|
33
|
+
publicRoutes = [],
|
|
34
|
+
signInUrl = "/sign-in",
|
|
35
|
+
afterAuth
|
|
36
|
+
} = options;
|
|
37
|
+
return async function middleware(req) {
|
|
38
|
+
const { pathname } = req.nextUrl;
|
|
39
|
+
const publicRoute = isPublic(pathname, publicRoutes);
|
|
40
|
+
let result = {
|
|
41
|
+
userId: null,
|
|
42
|
+
orgId: null,
|
|
43
|
+
orgRole: null,
|
|
44
|
+
sessionId: null,
|
|
45
|
+
riskLevel: null,
|
|
46
|
+
isSignedIn: false,
|
|
47
|
+
isPublicRoute: publicRoute
|
|
48
|
+
};
|
|
49
|
+
const sessionCookie = req.cookies.get("vaultix-session")?.value ?? extractBearer(req.headers.get("authorization") ?? "");
|
|
50
|
+
if (sessionCookie) {
|
|
51
|
+
const key = await getPublicKey();
|
|
52
|
+
if (key) {
|
|
53
|
+
try {
|
|
54
|
+
const { payload } = await jwtVerify(sessionCookie, key, {
|
|
55
|
+
algorithms: ["RS256"]
|
|
56
|
+
});
|
|
57
|
+
result = {
|
|
58
|
+
userId: payload["uid"] ?? null,
|
|
59
|
+
orgId: payload["org"] ?? null,
|
|
60
|
+
orgRole: payload["rol"] ?? null,
|
|
61
|
+
sessionId: payload["sid"] ?? null,
|
|
62
|
+
riskLevel: payload["risk"] ?? "low",
|
|
63
|
+
isSignedIn: true,
|
|
64
|
+
isPublicRoute: publicRoute
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (afterAuth) {
|
|
71
|
+
const override = afterAuth(result, req);
|
|
72
|
+
if (override) return override;
|
|
73
|
+
}
|
|
74
|
+
if (!result.isSignedIn && !publicRoute) {
|
|
75
|
+
const dest = new URL(signInUrl, req.url);
|
|
76
|
+
dest.searchParams.set("redirect_url", req.url);
|
|
77
|
+
return NextResponse.redirect(dest);
|
|
78
|
+
}
|
|
79
|
+
const next = new Headers(req.headers);
|
|
80
|
+
if (result.userId) {
|
|
81
|
+
next.set(HEADER_USER_ID, result.userId);
|
|
82
|
+
next.set(HEADER_ORG_ID, result.orgId ?? "");
|
|
83
|
+
next.set(HEADER_ORG_ROLE, result.orgRole ?? "");
|
|
84
|
+
next.set(HEADER_SESSION_ID, result.sessionId ?? "");
|
|
85
|
+
next.set(HEADER_RISK_LEVEL, result.riskLevel ?? "low");
|
|
86
|
+
} else {
|
|
87
|
+
[HEADER_USER_ID, HEADER_ORG_ID, HEADER_ORG_ROLE, HEADER_SESSION_ID, HEADER_RISK_LEVEL].forEach((h) => next.delete(h));
|
|
88
|
+
}
|
|
89
|
+
return NextResponse.next({ request: { headers: next } });
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function extractBearer(header) {
|
|
93
|
+
return header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
HEADER_ORG_ID,
|
|
97
|
+
HEADER_ORG_ROLE,
|
|
98
|
+
HEADER_RISK_LEVEL,
|
|
99
|
+
HEADER_SESSION_ID,
|
|
100
|
+
HEADER_USER_ID,
|
|
101
|
+
authMiddleware
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=middleware.mjs.map
|
|
@@ -0,0 +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 * 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// Importing an RSA public key is CPU-bound work; cache it across invocations.\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, \"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// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@smritix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/sign-in\", \"/sign-up\"] });\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 // Attempt to verify the session cookie.\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 // Clear any previously-set headers (defence-in-depth).\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,YAAY,iBAA+B;AACpD,SAAsB,oBAAoB;AAuCnC,IAAM,iBAAmB;AACzB,IAAM,gBAAmB;AACzB,IAAM,kBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAKjC,IAAI,YAA4B;AAChC,IAAI,YAA2B;AAE/B,eAAe,eAAwC;AACrD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,aAAa,cAAc,IAAK,QAAO;AAE3C,MAAI;AACF,gBAAY,MAAM,WAAW,KAAK,OAAO;AACzC,gBAAY;AACZ,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,SAAS,UAAkB,OAAwC;AAC1E,SAAO,MAAM,KAAK,CAAC,SAAS;AAC1B,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO,aAAa,QAAQ,SAAS,WAAW,IAAI;AAAA,IACtD;AACA,WAAO,KAAK,KAAK,QAAQ;AAAA,EAC3B,CAAC;AACH;AAaO,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,QAAM;AAAA,IACJ,eAAe,CAAC;AAAA,IAChB,YAAY;AAAA,IACZ;AAAA,EACF,IAAI;AAEJ,SAAO,eAAe,WAAW,KAAyC;AACxE,UAAM,EAAE,SAAS,IAAI,IAAI;AACzB,UAAM,cAAc,SAAS,UAAU,YAAY;AAGnD,QAAI,SAAqB;AAAA,MACvB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,eAAe;AAAA,IACjB;AAEA,UAAM,gBACJ,IAAI,QAAQ,IAAI,iBAAiB,GAAG,SACpC,cAAc,IAAI,QAAQ,IAAI,eAAe,KAAK,EAAE;AAEtD,QAAI,eAAe;AACjB,YAAM,MAAM,MAAM,aAAa;AAC/B,UAAI,KAAK;AACP,YAAI;AACF,gBAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,eAAe,KAAK;AAAA,YACtD,YAAY,CAAC,OAAO;AAAA,UACtB,CAAC;AAED,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;AAEL,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":[]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { VaultixOrganization, VaultixUser } from '@smritix.ai/react';
|
|
2
|
+
|
|
3
|
+
interface AuthObject {
|
|
4
|
+
userId: string | null;
|
|
5
|
+
orgId: string | null;
|
|
6
|
+
orgRole: string | null;
|
|
7
|
+
sessionId: string | null;
|
|
8
|
+
riskLevel: "low" | "medium" | "high" | "critical" | null;
|
|
9
|
+
isSignedIn: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current auth state by reading the headers injected by
|
|
13
|
+
* `authMiddleware`. Call this in Server Components, Route Handlers, and
|
|
14
|
+
* Server Actions — never in Client Components.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* import { auth } from "@smritix.ai/nextjs/server";
|
|
18
|
+
* export default async function Page() {
|
|
19
|
+
* const { userId, orgId } = await auth();
|
|
20
|
+
* …
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
declare function auth(): Promise<AuthObject>;
|
|
24
|
+
/**
|
|
25
|
+
* Asserts that the current user is authenticated. Redirects to `/sign-in`
|
|
26
|
+
* if not. Returns the auth object for convenience.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* export default async function ProtectedPage() {
|
|
30
|
+
* const { userId } = await protect();
|
|
31
|
+
* …
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
declare function protect(redirectTo?: string): Promise<AuthObject>;
|
|
35
|
+
/**
|
|
36
|
+
* Fetches the full `VaultixUser` record from the auth engine using server-to-server
|
|
37
|
+
* credentials (`VAULTIX_API_URL` + `VAULTIX_SECRET_KEY` env vars).
|
|
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.
|
|
41
|
+
*/
|
|
42
|
+
declare function currentUser(): Promise<VaultixUser | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Fetches the active `VaultixOrganization` from the auth engine.
|
|
45
|
+
* Returns null when the user has no active org or env vars are missing.
|
|
46
|
+
*
|
|
47
|
+
* Responses are cached for 30 seconds — org data changes infrequently.
|
|
48
|
+
*/
|
|
49
|
+
declare function currentOrg(): Promise<VaultixOrganization | null>;
|
|
50
|
+
|
|
51
|
+
export { type AuthObject, auth, currentOrg, currentUser, protect };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { VaultixOrganization, VaultixUser } from '@smritix.ai/react';
|
|
2
|
+
|
|
3
|
+
interface AuthObject {
|
|
4
|
+
userId: string | null;
|
|
5
|
+
orgId: string | null;
|
|
6
|
+
orgRole: string | null;
|
|
7
|
+
sessionId: string | null;
|
|
8
|
+
riskLevel: "low" | "medium" | "high" | "critical" | null;
|
|
9
|
+
isSignedIn: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current auth state by reading the headers injected by
|
|
13
|
+
* `authMiddleware`. Call this in Server Components, Route Handlers, and
|
|
14
|
+
* Server Actions — never in Client Components.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* import { auth } from "@smritix.ai/nextjs/server";
|
|
18
|
+
* export default async function Page() {
|
|
19
|
+
* const { userId, orgId } = await auth();
|
|
20
|
+
* …
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
declare function auth(): Promise<AuthObject>;
|
|
24
|
+
/**
|
|
25
|
+
* Asserts that the current user is authenticated. Redirects to `/sign-in`
|
|
26
|
+
* if not. Returns the auth object for convenience.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* export default async function ProtectedPage() {
|
|
30
|
+
* const { userId } = await protect();
|
|
31
|
+
* …
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
declare function protect(redirectTo?: string): Promise<AuthObject>;
|
|
35
|
+
/**
|
|
36
|
+
* Fetches the full `VaultixUser` record from the auth engine using server-to-server
|
|
37
|
+
* credentials (`VAULTIX_API_URL` + `VAULTIX_SECRET_KEY` env vars).
|
|
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.
|
|
41
|
+
*/
|
|
42
|
+
declare function currentUser(): Promise<VaultixUser | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Fetches the active `VaultixOrganization` from the auth engine.
|
|
45
|
+
* Returns null when the user has no active org or env vars are missing.
|
|
46
|
+
*
|
|
47
|
+
* Responses are cached for 30 seconds — org data changes infrequently.
|
|
48
|
+
*/
|
|
49
|
+
declare function currentOrg(): Promise<VaultixOrganization | null>;
|
|
50
|
+
|
|
51
|
+
export { type AuthObject, auth, currentOrg, currentUser, protect };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
auth: () => auth,
|
|
24
|
+
currentOrg: () => currentOrg,
|
|
25
|
+
currentUser: () => currentUser,
|
|
26
|
+
protect: () => protect
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(server_exports);
|
|
29
|
+
var import_headers = require("next/headers");
|
|
30
|
+
var import_navigation = require("next/navigation");
|
|
31
|
+
|
|
32
|
+
// src/middleware.ts
|
|
33
|
+
var import_jose = require("jose");
|
|
34
|
+
var import_server = require("next/server");
|
|
35
|
+
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
36
|
+
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
37
|
+
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
38
|
+
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
39
|
+
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
40
|
+
|
|
41
|
+
// src/server.ts
|
|
42
|
+
async function auth() {
|
|
43
|
+
const h = await (0, import_headers.headers)();
|
|
44
|
+
const userId = h.get(HEADER_USER_ID);
|
|
45
|
+
if (!userId) {
|
|
46
|
+
return {
|
|
47
|
+
userId: null,
|
|
48
|
+
orgId: null,
|
|
49
|
+
orgRole: null,
|
|
50
|
+
sessionId: null,
|
|
51
|
+
riskLevel: null,
|
|
52
|
+
isSignedIn: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
userId,
|
|
57
|
+
orgId: h.get(HEADER_ORG_ID) || null,
|
|
58
|
+
orgRole: h.get(HEADER_ORG_ROLE) || null,
|
|
59
|
+
sessionId: h.get(HEADER_SESSION_ID) || null,
|
|
60
|
+
riskLevel: h.get(HEADER_RISK_LEVEL) ?? "low",
|
|
61
|
+
isSignedIn: true
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function protect(redirectTo = "/sign-in") {
|
|
65
|
+
const authObj = await auth();
|
|
66
|
+
if (!authObj.isSignedIn) {
|
|
67
|
+
(0, import_navigation.redirect)(redirectTo);
|
|
68
|
+
}
|
|
69
|
+
return authObj;
|
|
70
|
+
}
|
|
71
|
+
async function currentUser() {
|
|
72
|
+
const { userId } = await auth();
|
|
73
|
+
if (!userId) return null;
|
|
74
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
75
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
76
|
+
if (!apiUrl || !secret) return null;
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(`${apiUrl}/v1/users/${userId}`, {
|
|
79
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
80
|
+
cache: "no-store"
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) return null;
|
|
83
|
+
return await res.json();
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function currentOrg() {
|
|
89
|
+
const { orgId } = await auth();
|
|
90
|
+
if (!orgId) return null;
|
|
91
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
92
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
93
|
+
if (!apiUrl || !secret) return null;
|
|
94
|
+
try {
|
|
95
|
+
const orgFetchInit = {
|
|
96
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
97
|
+
next: { revalidate: 30 }
|
|
98
|
+
};
|
|
99
|
+
const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit);
|
|
100
|
+
if (!res.ok) return null;
|
|
101
|
+
return await res.json();
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
107
|
+
0 && (module.exports = {
|
|
108
|
+
auth,
|
|
109
|
+
currentOrg,
|
|
110
|
+
currentUser,
|
|
111
|
+
protect
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/middleware.ts"],"sourcesContent":["// This file is server-only. Import from \"@smritix.ai/nextjs/server\".\n// Do NOT import in Client Components — it will throw at runtime.\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport type { VaultixOrganization, VaultixUser } from \"@smritix.ai/react\";\nimport {\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_RISK_LEVEL,\n HEADER_SESSION_ID,\n HEADER_USER_ID,\n} from \"./middleware\";\n\n// Next.js extends RequestInit with cache and next options for its data cache.\ntype NextRequestInit = RequestInit & {\n cache?: RequestCache;\n next?: { revalidate?: number | false; tags?: string[] };\n};\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\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state by reading the headers injected by\n * `authMiddleware`. Call this in Server Components, Route Handlers, and\n * Server Actions — never in Client Components.\n *\n * @example\n * import { auth } from \"@smritix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId, orgId } = await auth();\n * …\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n\n if (!userId) {\n return {\n userId: null,\n orgId: null,\n orgRole: null,\n sessionId: null,\n riskLevel: null,\n isSignedIn: false,\n };\n }\n\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 };\n}\n\n// ─── protect() ────────────────────────────────────────────────────────────────\n\n/**\n * Asserts that the current user is authenticated. Redirects to `/sign-in`\n * if not. Returns the auth object for convenience.\n *\n * @example\n * export default async function ProtectedPage() {\n * const { userId } = await protect();\n * …\n * }\n */\nexport async function protect(redirectTo = \"/sign-in\"): Promise<AuthObject> {\n const authObj = await auth();\n if (!authObj.isSignedIn) {\n redirect(redirectTo);\n }\n return authObj;\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the full `VaultixUser` record from the auth engine using server-to-server\n * credentials (`VAULTIX_API_URL` + `VAULTIX_SECRET_KEY` env vars).\n * Returns null when unauthenticated or when env vars are missing.\n *\n * Responses are never cached (`cache: \"no-store\"`) — user data must be fresh.\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const { userId } = await auth();\n if (!userId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/v1/users/${userId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n return (await res.json()) as VaultixUser;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the active `VaultixOrganization` from the auth engine.\n * Returns null when the user has no active org or env vars are missing.\n *\n * Responses are cached for 30 seconds — org data changes infrequently.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const orgFetchInit: NextRequestInit = {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n };\n const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n","// 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 * 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// Importing an RSA public key is CPU-bound work; cache it across invocations.\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, \"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// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@smritix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/sign-in\", \"/sign-up\"] });\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 // Attempt to verify the session cookie.\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 // Clear any previously-set headers (defence-in-depth).\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;AAEA,qBAAwB;AACxB,wBAAyB;;;ACFzB,kBAAoD;AACpD,oBAA0C;AAuCnC,IAAM,iBAAmB;AACzB,IAAM,gBAAmB;AACzB,IAAM,kBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;;;ADDjC,eAAsB,OAA4B;AAChD,QAAM,IAAI,UAAM,wBAAQ;AACxB,QAAM,SAAS,EAAE,IAAI,cAAc;AAEnC,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,MACX,YAAY;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,OAAW,EAAE,IAAI,aAAa,KAAS;AAAA,IACvC,SAAW,EAAE,IAAI,eAAe,KAAO;AAAA,IACvC,WAAW,EAAE,IAAI,iBAAiB,KAAK;AAAA,IACvC,WAAY,EAAE,IAAI,iBAAiB,KAAK;AAAA,IACxC,YAAY;AAAA,EACd;AACF;AAcA,eAAsB,QAAQ,aAAa,YAAiC;AAC1E,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,YAAY;AACvB,oCAAS,UAAU;AAAA,EACrB;AACA,SAAO;AACT;AAWA,eAAsB,cAA2C;AAC/D,QAAM,EAAE,OAAO,IAAI,MAAM,KAAK;AAC9B,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,SAAS,QAAQ,IAAI,iBAAiB;AAC5C,QAAM,SAAS,QAAQ,IAAI,oBAAoB;AAC/C,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,aAAa,MAAM,IAAI;AAAA,MACtD,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG;AAAA,MAC7C,OAAO;AAAA,IACT,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAsB,aAAkD;AACtE,QAAM,EAAE,MAAM,IAAI,MAAM,KAAK;AAC7B,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,SAAS,QAAQ,IAAI,iBAAiB;AAC5C,QAAM,SAAS,QAAQ,IAAI,oBAAoB;AAC/C,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,MAAI;AACF,UAAM,eAAgC;AAAA,MACpC,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG;AAAA,MAC7C,MAAM,EAAE,YAAY,GAAG;AAAA,IACzB;AACA,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,YAAY,KAAK,IAAI,YAA2B;AACjF,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { headers } from "next/headers";
|
|
3
|
+
import { redirect } from "next/navigation";
|
|
4
|
+
|
|
5
|
+
// src/middleware.ts
|
|
6
|
+
import { importSPKI, jwtVerify } from "jose";
|
|
7
|
+
import { NextResponse } from "next/server";
|
|
8
|
+
var HEADER_USER_ID = "x-vaultix-user-id";
|
|
9
|
+
var HEADER_ORG_ID = "x-vaultix-org-id";
|
|
10
|
+
var HEADER_ORG_ROLE = "x-vaultix-org-role";
|
|
11
|
+
var HEADER_SESSION_ID = "x-vaultix-session-id";
|
|
12
|
+
var HEADER_RISK_LEVEL = "x-vaultix-risk-level";
|
|
13
|
+
|
|
14
|
+
// src/server.ts
|
|
15
|
+
async function auth() {
|
|
16
|
+
const h = await headers();
|
|
17
|
+
const userId = h.get(HEADER_USER_ID);
|
|
18
|
+
if (!userId) {
|
|
19
|
+
return {
|
|
20
|
+
userId: null,
|
|
21
|
+
orgId: null,
|
|
22
|
+
orgRole: null,
|
|
23
|
+
sessionId: null,
|
|
24
|
+
riskLevel: null,
|
|
25
|
+
isSignedIn: false
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
userId,
|
|
30
|
+
orgId: h.get(HEADER_ORG_ID) || null,
|
|
31
|
+
orgRole: h.get(HEADER_ORG_ROLE) || null,
|
|
32
|
+
sessionId: h.get(HEADER_SESSION_ID) || null,
|
|
33
|
+
riskLevel: h.get(HEADER_RISK_LEVEL) ?? "low",
|
|
34
|
+
isSignedIn: true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function protect(redirectTo = "/sign-in") {
|
|
38
|
+
const authObj = await auth();
|
|
39
|
+
if (!authObj.isSignedIn) {
|
|
40
|
+
redirect(redirectTo);
|
|
41
|
+
}
|
|
42
|
+
return authObj;
|
|
43
|
+
}
|
|
44
|
+
async function currentUser() {
|
|
45
|
+
const { userId } = await auth();
|
|
46
|
+
if (!userId) return null;
|
|
47
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
48
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
49
|
+
if (!apiUrl || !secret) return null;
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(`${apiUrl}/v1/users/${userId}`, {
|
|
52
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
53
|
+
cache: "no-store"
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) return null;
|
|
56
|
+
return await res.json();
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function currentOrg() {
|
|
62
|
+
const { orgId } = await auth();
|
|
63
|
+
if (!orgId) return null;
|
|
64
|
+
const apiUrl = process.env["VAULTIX_API_URL"];
|
|
65
|
+
const secret = process.env["VAULTIX_SECRET_KEY"];
|
|
66
|
+
if (!apiUrl || !secret) return null;
|
|
67
|
+
try {
|
|
68
|
+
const orgFetchInit = {
|
|
69
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
70
|
+
next: { revalidate: 30 }
|
|
71
|
+
};
|
|
72
|
+
const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit);
|
|
73
|
+
if (!res.ok) return null;
|
|
74
|
+
return await res.json();
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
auth,
|
|
81
|
+
currentOrg,
|
|
82
|
+
currentUser,
|
|
83
|
+
protect
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/middleware.ts"],"sourcesContent":["// This file is server-only. Import from \"@smritix.ai/nextjs/server\".\n// Do NOT import in Client Components — it will throw at runtime.\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport type { VaultixOrganization, VaultixUser } from \"@smritix.ai/react\";\nimport {\n HEADER_ORG_ID,\n HEADER_ORG_ROLE,\n HEADER_RISK_LEVEL,\n HEADER_SESSION_ID,\n HEADER_USER_ID,\n} from \"./middleware\";\n\n// Next.js extends RequestInit with cache and next options for its data cache.\ntype NextRequestInit = RequestInit & {\n cache?: RequestCache;\n next?: { revalidate?: number | false; tags?: string[] };\n};\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\n// ─── auth() ───────────────────────────────────────────────────────────────────\n\n/**\n * Returns the current auth state by reading the headers injected by\n * `authMiddleware`. Call this in Server Components, Route Handlers, and\n * Server Actions — never in Client Components.\n *\n * @example\n * import { auth } from \"@smritix.ai/nextjs/server\";\n * export default async function Page() {\n * const { userId, orgId } = await auth();\n * …\n * }\n */\nexport async function auth(): Promise<AuthObject> {\n const h = await headers();\n const userId = h.get(HEADER_USER_ID);\n\n if (!userId) {\n return {\n userId: null,\n orgId: null,\n orgRole: null,\n sessionId: null,\n riskLevel: null,\n isSignedIn: false,\n };\n }\n\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 };\n}\n\n// ─── protect() ────────────────────────────────────────────────────────────────\n\n/**\n * Asserts that the current user is authenticated. Redirects to `/sign-in`\n * if not. Returns the auth object for convenience.\n *\n * @example\n * export default async function ProtectedPage() {\n * const { userId } = await protect();\n * …\n * }\n */\nexport async function protect(redirectTo = \"/sign-in\"): Promise<AuthObject> {\n const authObj = await auth();\n if (!authObj.isSignedIn) {\n redirect(redirectTo);\n }\n return authObj;\n}\n\n// ─── currentUser() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the full `VaultixUser` record from the auth engine using server-to-server\n * credentials (`VAULTIX_API_URL` + `VAULTIX_SECRET_KEY` env vars).\n * Returns null when unauthenticated or when env vars are missing.\n *\n * Responses are never cached (`cache: \"no-store\"`) — user data must be fresh.\n */\nexport async function currentUser(): Promise<VaultixUser | null> {\n const { userId } = await auth();\n if (!userId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const res = await fetch(`${apiUrl}/v1/users/${userId}`, {\n headers: { Authorization: `Bearer ${secret}` },\n cache: \"no-store\",\n });\n if (!res.ok) return null;\n return (await res.json()) as VaultixUser;\n } catch {\n return null;\n }\n}\n\n// ─── currentOrg() ────────────────────────────────────────────────────────────\n\n/**\n * Fetches the active `VaultixOrganization` from the auth engine.\n * Returns null when the user has no active org or env vars are missing.\n *\n * Responses are cached for 30 seconds — org data changes infrequently.\n */\nexport async function currentOrg(): Promise<VaultixOrganization | null> {\n const { orgId } = await auth();\n if (!orgId) return null;\n\n const apiUrl = process.env[\"VAULTIX_API_URL\"];\n const secret = process.env[\"VAULTIX_SECRET_KEY\"];\n if (!apiUrl || !secret) return null;\n\n try {\n const orgFetchInit: NextRequestInit = {\n headers: { Authorization: `Bearer ${secret}` },\n next: { revalidate: 30 },\n };\n const res = await fetch(`${apiUrl}/v1/orgs/${orgId}`, orgFetchInit as RequestInit);\n if (!res.ok) return null;\n return (await res.json()) as VaultixOrganization;\n } catch {\n return null;\n }\n}\n","// 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 * 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// Importing an RSA public key is CPU-bound work; cache it across invocations.\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, \"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// ─── authMiddleware factory ───────────────────────────────────────────────────\n\n/**\n * authMiddleware wraps your Next.js middleware.ts export.\n *\n * @example\n * // middleware.ts\n * import { authMiddleware } from \"@smritix.ai/nextjs/middleware\";\n * export default authMiddleware({ publicRoutes: [\"/sign-in\", \"/sign-up\"] });\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 // Attempt to verify the session cookie.\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 // Clear any previously-set headers (defence-in-depth).\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":";AAEA,SAAS,eAAe;AACxB,SAAS,gBAAgB;;;ACFzB,SAAS,YAAY,iBAA+B;AACpD,SAAsB,oBAAoB;AAuCnC,IAAM,iBAAmB;AACzB,IAAM,gBAAmB;AACzB,IAAM,kBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;;;ADDjC,eAAsB,OAA4B;AAChD,QAAM,IAAI,MAAM,QAAQ;AACxB,QAAM,SAAS,EAAE,IAAI,cAAc;AAEnC,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,MACX,YAAY;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,OAAW,EAAE,IAAI,aAAa,KAAS;AAAA,IACvC,SAAW,EAAE,IAAI,eAAe,KAAO;AAAA,IACvC,WAAW,EAAE,IAAI,iBAAiB,KAAK;AAAA,IACvC,WAAY,EAAE,IAAI,iBAAiB,KAAK;AAAA,IACxC,YAAY;AAAA,EACd;AACF;AAcA,eAAsB,QAAQ,aAAa,YAAiC;AAC1E,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,YAAY;AACvB,aAAS,UAAU;AAAA,EACrB;AACA,SAAO;AACT;AAWA,eAAsB,cAA2C;AAC/D,QAAM,EAAE,OAAO,IAAI,MAAM,KAAK;AAC9B,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,SAAS,QAAQ,IAAI,iBAAiB;AAC5C,QAAM,SAAS,QAAQ,IAAI,oBAAoB;AAC/C,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,aAAa,MAAM,IAAI;AAAA,MACtD,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG;AAAA,MAC7C,OAAO;AAAA,IACT,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAsB,aAAkD;AACtE,QAAM,EAAE,MAAM,IAAI,MAAM,KAAK;AAC7B,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,SAAS,QAAQ,IAAI,iBAAiB;AAC5C,QAAM,SAAS,QAAQ,IAAI,oBAAoB;AAC/C,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,MAAI;AACF,UAAM,eAAgC;AAAA,MACpC,SAAS,EAAE,eAAe,UAAU,MAAM,GAAG;AAAA,MAC7C,MAAM,EAAE,YAAY,GAAG;AAAA,IACzB;AACA,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,YAAY,KAAK,IAAI,YAA2B;AACjF,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smritix.ai/nextjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vaultix-ID Next.js SDK — edge middleware and server utilities",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./server": {
|
|
16
|
+
"types": "./dist/server.d.ts",
|
|
17
|
+
"import": "./dist/server.mjs",
|
|
18
|
+
"require": "./dist/server.js"
|
|
19
|
+
},
|
|
20
|
+
"./middleware": {
|
|
21
|
+
"types": "./dist/middleware.d.ts",
|
|
22
|
+
"import": "./dist/middleware.mjs",
|
|
23
|
+
"require": "./dist/middleware.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup --format esm,cjs --dts",
|
|
28
|
+
"dev": "tsup --format esm,cjs --dts --watch",
|
|
29
|
+
"lint": "tsc --noEmit",
|
|
30
|
+
"test": "jest --passWithNoTests",
|
|
31
|
+
"test:coverage": "jest --coverage"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"next": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@smritix.ai/react": "^0.1.0",
|
|
38
|
+
"jose": "^5.6.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@vaultix/config-ts": "workspace:*",
|
|
42
|
+
"next": "^14.2.0",
|
|
43
|
+
"typescript": "^5.5.0",
|
|
44
|
+
"tsup": "^8.0.0",
|
|
45
|
+
"jest": "^29.0.0",
|
|
46
|
+
"jest-environment-jsdom": "^29.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|