@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.
@@ -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';
@@ -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":"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","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":"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","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 };
@@ -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":[]}
@@ -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
+ }