next-supa-utils 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +145 -3
- package/dist/client/index.cjs +187 -32
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +190 -1
- package/dist/client/index.d.ts +190 -1
- package/dist/client/index.js +177 -26
- package/dist/client/index.js.map +1 -1
- package/dist/server/index.cjs +92 -7
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +97 -2
- package/dist/server/index.d.ts +97 -2
- package/dist/server/index.js +91 -7
- package/dist/server/index.js.map +1 -1
- package/dist/shared/index.d.cts +10 -0
- package/dist/shared/index.d.ts +10 -0
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -40,8 +40,8 @@ function withSupaAuth(options) {
|
|
|
40
40
|
let response = NextResponse.next({
|
|
41
41
|
request: { headers: request.headers }
|
|
42
42
|
});
|
|
43
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
44
|
-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
43
|
+
const supabaseUrl = options.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
44
|
+
const supabaseAnonKey = options.supabaseAnonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
45
45
|
if (!supabaseUrl || !supabaseAnonKey) {
|
|
46
46
|
console.error(
|
|
47
47
|
"[next-supa-utils] Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables."
|
|
@@ -124,11 +124,11 @@ function handleSupaError(error) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// src/server/actions/actionWrapper.ts
|
|
127
|
-
function createAction(fn) {
|
|
127
|
+
function createAction(fn, options) {
|
|
128
128
|
return async (...args) => {
|
|
129
129
|
try {
|
|
130
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
131
|
-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
130
|
+
const supabaseUrl = options?.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
131
|
+
const supabaseAnonKey = options?.supabaseAnonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
132
132
|
if (!supabaseUrl || !supabaseAnonKey) {
|
|
133
133
|
return {
|
|
134
134
|
data: null,
|
|
@@ -146,8 +146,8 @@ function createAction(fn) {
|
|
|
146
146
|
},
|
|
147
147
|
setAll(cookiesToSet) {
|
|
148
148
|
try {
|
|
149
|
-
cookiesToSet.forEach(({ name, value, options }) => {
|
|
150
|
-
cookieStore.set(name, value,
|
|
149
|
+
cookiesToSet.forEach(({ name, value, options: options2 }) => {
|
|
150
|
+
cookieStore.set(name, value, options2);
|
|
151
151
|
});
|
|
152
152
|
} catch {
|
|
153
153
|
}
|
|
@@ -162,8 +162,92 @@ function createAction(fn) {
|
|
|
162
162
|
}
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
// src/server/actions/routeWrapper.ts
|
|
167
|
+
import { createServerClient as createServerClient3 } from "@supabase/ssr";
|
|
168
|
+
import { NextResponse as NextResponse2 } from "next/server";
|
|
169
|
+
import { cookies as cookies2 } from "next/headers";
|
|
170
|
+
function routeWrapper(handler, options) {
|
|
171
|
+
const { requireAuth = false, supabaseUrl: optUrl, supabaseAnonKey: optKey } = options ?? {};
|
|
172
|
+
return async (request, routeContext) => {
|
|
173
|
+
try {
|
|
174
|
+
const supabaseUrl = optUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
175
|
+
const supabaseAnonKey = optKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
176
|
+
if (!supabaseUrl || !supabaseAnonKey) {
|
|
177
|
+
return NextResponse2.json(
|
|
178
|
+
{ error: { message: "Server configuration error", code: "CONFIG_ERROR" } },
|
|
179
|
+
{ status: 500 }
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const ctx = {
|
|
183
|
+
params: routeContext?.params ? await routeContext.params : {},
|
|
184
|
+
supabase: null,
|
|
185
|
+
user: null
|
|
186
|
+
};
|
|
187
|
+
if (requireAuth) {
|
|
188
|
+
const cookieStore = await cookies2();
|
|
189
|
+
const supabase = createServerClient3(supabaseUrl, supabaseAnonKey, {
|
|
190
|
+
cookies: {
|
|
191
|
+
getAll() {
|
|
192
|
+
return cookieStore.getAll();
|
|
193
|
+
},
|
|
194
|
+
setAll(cookiesToSet) {
|
|
195
|
+
try {
|
|
196
|
+
cookiesToSet.forEach(({ name, value, options: opts }) => {
|
|
197
|
+
cookieStore.set(name, value, opts);
|
|
198
|
+
});
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
const {
|
|
205
|
+
data: { user },
|
|
206
|
+
error: authError
|
|
207
|
+
} = await supabase.auth.getUser();
|
|
208
|
+
if (authError || !user) {
|
|
209
|
+
return NextResponse2.json(
|
|
210
|
+
{
|
|
211
|
+
error: {
|
|
212
|
+
message: "Unauthorized: valid session required",
|
|
213
|
+
code: "UNAUTHORIZED"
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
{ status: 401 }
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
ctx.supabase = supabase;
|
|
220
|
+
ctx.user = user;
|
|
221
|
+
} else {
|
|
222
|
+
const cookieStore = await cookies2();
|
|
223
|
+
ctx.supabase = createServerClient3(supabaseUrl, supabaseAnonKey, {
|
|
224
|
+
cookies: {
|
|
225
|
+
getAll() {
|
|
226
|
+
return cookieStore.getAll();
|
|
227
|
+
},
|
|
228
|
+
setAll(cookiesToSet) {
|
|
229
|
+
try {
|
|
230
|
+
cookiesToSet.forEach(({ name, value, options: opts }) => {
|
|
231
|
+
cookieStore.set(name, value, opts);
|
|
232
|
+
});
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const response = await handler(request, ctx);
|
|
240
|
+
return response instanceof NextResponse2 ? response : NextResponse2.json(await response.json(), { status: response.status });
|
|
241
|
+
} catch (caught) {
|
|
242
|
+
const error = handleSupaError(caught);
|
|
243
|
+
const status = error.status ?? 500;
|
|
244
|
+
return NextResponse2.json({ error }, { status });
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
165
248
|
export {
|
|
166
249
|
createAction,
|
|
250
|
+
routeWrapper,
|
|
167
251
|
withSupaAuth
|
|
168
252
|
};
|
|
169
253
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/middleware/withSupaAuth.ts","../../src/server/actions/actionWrapper.ts","../../src/shared/utils/error-handler.ts"],"sourcesContent":["import { createServerClient, type CookieOptions } from \"@supabase/ssr\";\nimport { NextResponse, type NextRequest } from \"next/server\";\n\nimport type { MiddlewareOptions, RouteConfig } from \"../../types\";\n\n// ── Path matching utility ───────────────────────────────────────────\n\n/**\n * Converts a route pattern into a RegExp for matching.\n *\n * Supports:\n * - Exact: `\"/settings\"` → matches only `/settings`\n * - Prefix: `\"/dashboard\"` → matches `/dashboard`, `/dashboard/stats`\n * - Wildcard: `\"/admin/:path*\"` → matches `/admin`, `/admin/users`, `/admin/a/b/c`\n */\nfunction matchPath(pattern: string, pathname: string): boolean {\n // Wildcard pattern: \"/admin/:path*\" → match \"/admin\" and everything below\n if (pattern.includes(\":path*\")) {\n const prefix = pattern.replace(/:path\\*$/, \"\").replace(/\\/$/, \"\");\n return pathname === prefix || pathname.startsWith(prefix + \"/\");\n }\n\n // Prefix matching: \"/dashboard\" matches \"/dashboard/anything\"\n return pathname === pattern || pathname.startsWith(pattern + \"/\");\n}\n\n/**\n * Finds the first `RouteConfig` whose pattern matches `pathname`, or `undefined`.\n */\nfunction findMatchingRoute(\n routes: RouteConfig[],\n pathname: string,\n): RouteConfig | undefined {\n return routes.find((route) => matchPath(route.path, pathname));\n}\n\n// ── Role extraction utility ─────────────────────────────────────────\n\ntype UserMeta = {\n user_metadata: Record<string, unknown>;\n app_metadata: Record<string, unknown>;\n};\n\nfunction extractRole(\n user: UserMeta,\n extractor: MiddlewareOptions[\"roleExtractor\"],\n): string | string[] | undefined {\n if (typeof extractor === \"function\") {\n return extractor(user);\n }\n\n const source =\n extractor === \"app_metadata\" ? user.app_metadata : user.user_metadata;\n\n const raw = source?.role;\n\n if (typeof raw === \"string\") return raw;\n if (Array.isArray(raw) && raw.every((r) => typeof r === \"string\")) {\n return raw as string[];\n }\n\n return undefined;\n}\n\n/**\n * Check whether the user's role(s) satisfy the route's `allowedRoles`.\n */\nfunction hasRequiredRole(\n userRole: string | string[] | undefined,\n allowedRoles: string[] | undefined,\n): boolean {\n // No role restriction → any authenticated user is allowed.\n if (!allowedRoles || allowedRoles.length === 0) return true;\n\n if (!userRole) return false;\n\n const roles = Array.isArray(userRole) ? userRole : [userRole];\n return roles.some((r) => allowedRoles.includes(r));\n}\n\n// ── Main middleware factory ─────────────────────────────────────────\n\n/**\n * Create a Next.js middleware handler that protects routes with\n * authentication **and** optional role-based access control (RBAC).\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { withSupaAuth } from \"next-supa-utils/server\";\n *\n * export default withSupaAuth({\n * routes: [\n * { path: \"/dashboard\" }, // any authed user\n * { path: \"/admin/:path*\", allowedRoles: [\"admin\"] }, // admin only\n * { path: \"/editor/:path*\", allowedRoles: [\"admin\", \"editor\"] },\n * ],\n * redirectTo: \"/login\",\n * roleExtractor: \"user_metadata\", // or \"app_metadata\" or a custom fn\n * });\n *\n * export const config = {\n * matcher: [\"/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)\"],\n * };\n * ```\n */\nexport function withSupaAuth(options: MiddlewareOptions) {\n const {\n routes,\n redirectTo = \"/login\",\n roleExtractor = \"user_metadata\",\n onAuthSuccess,\n } = options;\n\n return async function middleware(request: NextRequest): Promise<NextResponse> {\n // ── 1. Create a mutable response so Supabase can set cookies ──\n let response = NextResponse.next({\n request: { headers: request.headers },\n });\n\n const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n console.error(\n \"[next-supa-utils] Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n );\n return response;\n }\n\n // ── 2. Initialize server client with middleware cookie helpers ─\n const supabase = createServerClient(supabaseUrl, supabaseAnonKey, {\n cookies: {\n getAll() {\n return request.cookies.getAll();\n },\n setAll(cookiesToSet: { name: string; value: string; options?: CookieOptions }[]) {\n cookiesToSet.forEach(({ name, value }) => {\n request.cookies.set(name, value);\n });\n\n response = NextResponse.next({ request });\n\n cookiesToSet.forEach(({ name, value, options }) => {\n response.cookies.set(name, value, options);\n });\n },\n },\n });\n\n // ── 3. Refresh session (required to keep tokens alive) ────────\n const {\n data: { user },\n } = await supabase.auth.getUser();\n\n const { pathname } = request.nextUrl;\n\n // ── 4. Find the matching route config ─────────────────────────\n const matchedRoute = findMatchingRoute(routes, pathname);\n\n // No matching route → not a protected path, pass through.\n if (!matchedRoute) {\n return response;\n }\n\n // ── 5. Check authentication ───────────────────────────────────\n if (!user) {\n const loginUrl = new URL(redirectTo, request.url);\n loginUrl.searchParams.set(\"next\", pathname);\n return NextResponse.redirect(loginUrl);\n }\n\n // ── 6. Check role-based access ────────────────────────────────\n const userRole = extractRole(user, roleExtractor);\n\n if (!hasRequiredRole(userRole, matchedRoute.allowedRoles)) {\n // User is logged in but lacks the required role.\n const forbiddenUrl = new URL(redirectTo, request.url);\n forbiddenUrl.searchParams.set(\"error\", \"forbidden\");\n forbiddenUrl.searchParams.set(\"next\", pathname);\n return NextResponse.redirect(forbiddenUrl);\n }\n\n // ── 7. Success callback ───────────────────────────────────────\n if (onAuthSuccess) {\n await onAuthSuccess({\n id: user.id,\n email: user.email ?? undefined,\n role: userRole,\n });\n }\n\n return response;\n };\n}\n","import { createServerClient, type CookieOptions } from \"@supabase/ssr\";\nimport { cookies } from \"next/headers\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\nimport type { ActionResponse, SupaError } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\n\n/**\n * Create a type-safe Server Action that automatically:\n * 1. Initialises a Supabase server client (with cookies)\n * 2. Wraps execution in try/catch\n * 3. Returns a standardised `{ data, error }` response\n *\n * @example\n * ```ts\n * // app/actions/profile.ts\n * \"use server\";\n * import { createAction } from \"next-supa-utils/server\";\n *\n * export const getProfile = createAction(async (supabase, userId: string) => {\n * const { data, error } = await supabase\n * .from(\"profiles\")\n * .select(\"*\")\n * .eq(\"id\", userId)\n * .single();\n *\n * if (error) throw error;\n * return data;\n * });\n *\n * // Usage in a Server Component or Client Component:\n * const result = await getProfile(\"user-uuid\");\n * if (result.error) { ... }\n * ```\n */\nexport function createAction<TArgs extends unknown[], TResult>(\n fn: (supabase: SupabaseClient, ...args: TArgs) => Promise<TResult>,\n): (...args: TArgs) => Promise<ActionResponse<TResult>> {\n return async (...args: TArgs): Promise<ActionResponse<TResult>> => {\n try {\n const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n return {\n data: null,\n error: {\n message:\n \"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n code: \"CONFIG_ERROR\",\n },\n };\n }\n\n const cookieStore = await cookies();\n\n const supabase = createServerClient(supabaseUrl, supabaseAnonKey, {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: { name: string; value: string; options?: CookieOptions }[]) {\n try {\n cookiesToSet.forEach(({ name, value, options }) => {\n cookieStore.set(name, value, options);\n });\n } catch {\n // `cookies().set()` throws when called from a Server Component.\n // In that context we only need read access — the middleware\n // handles token refresh.\n }\n },\n },\n });\n\n const data = await fn(supabase, ...args);\n\n return { data, error: null };\n } catch (caught: unknown) {\n const error: SupaError = handleSupaError(caught);\n return { data: null, error };\n }\n };\n}\n","import type { SupaError } from \"../../types\";\n\n/**\n * Normalize any thrown value into a consistent `SupaError` shape.\n *\n * Handles:\n * - Supabase `AuthError` / `PostgrestError` (has `.message` and optional `.code` / `.status`)\n * - Standard `Error` instances\n * - Plain strings\n * - Unknown values (fallback)\n */\nexport function handleSupaError(error: unknown): SupaError {\n // ── Supabase errors & standard Error instances ──────────────────\n if (error instanceof Error) {\n const record = error as unknown as Record<string, unknown>;\n return {\n message: error.message,\n code: typeof record.code === \"string\" ? record.code : undefined,\n status: typeof record.status === \"number\" ? record.status : undefined,\n };\n }\n\n // ── Plain object with a message property ────────────────────────\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof (error as Record<string, unknown>).message === \"string\"\n ) {\n const err = error as Record<string, unknown>;\n return {\n message: err.message as string,\n code: typeof err.code === \"string\" ? err.code : undefined,\n status: typeof err.status === \"number\" ? err.status : undefined,\n };\n }\n\n // ── String ──────────────────────────────────────────────────────\n if (typeof error === \"string\") {\n return { message: error };\n }\n\n // ── Fallback ────────────────────────────────────────────────────\n return { message: \"An unknown error occurred\" };\n}\n"],"mappings":";AAAA,SAAS,0BAA8C;AACvD,SAAS,oBAAsC;AAc/C,SAAS,UAAU,SAAiB,UAA2B;AAE7D,MAAI,QAAQ,SAAS,QAAQ,GAAG;AAC9B,UAAM,SAAS,QAAQ,QAAQ,YAAY,EAAE,EAAE,QAAQ,OAAO,EAAE;AAChE,WAAO,aAAa,UAAU,SAAS,WAAW,SAAS,GAAG;AAAA,EAChE;AAGA,SAAO,aAAa,WAAW,SAAS,WAAW,UAAU,GAAG;AAClE;AAKA,SAAS,kBACP,QACA,UACyB;AACzB,SAAO,OAAO,KAAK,CAAC,UAAU,UAAU,MAAM,MAAM,QAAQ,CAAC;AAC/D;AASA,SAAS,YACP,MACA,WAC+B;AAC/B,MAAI,OAAO,cAAc,YAAY;AACnC,WAAO,UAAU,IAAI;AAAA,EACvB;AAEA,QAAM,SACJ,cAAc,iBAAiB,KAAK,eAAe,KAAK;AAE1D,QAAM,MAAM,QAAQ;AAEpB,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,MAAM,QAAQ,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,GAAG;AACjE,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKA,SAAS,gBACP,UACA,cACS;AAET,MAAI,CAAC,gBAAgB,aAAa,WAAW,EAAG,QAAO;AAEvD,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,QAAQ,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ;AAC5D,SAAO,MAAM,KAAK,CAAC,MAAM,aAAa,SAAS,CAAC,CAAC;AACnD;AA4BO,SAAS,aAAa,SAA4B;AACvD,QAAM;AAAA,IACJ;AAAA,IACA,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB;AAAA,EACF,IAAI;AAEJ,SAAO,eAAe,WAAW,SAA6C;AAE5E,QAAI,WAAW,aAAa,KAAK;AAAA,MAC/B,SAAS,EAAE,SAAS,QAAQ,QAAQ;AAAA,IACtC,CAAC;AAED,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,kBAAkB,QAAQ,IAAI;AAEpC,QAAI,CAAC,eAAe,CAAC,iBAAiB;AACpC,cAAQ;AAAA,QACN;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,mBAAmB,aAAa,iBAAiB;AAAA,MAChE,SAAS;AAAA,QACP,SAAS;AACP,iBAAO,QAAQ,QAAQ,OAAO;AAAA,QAChC;AAAA,QACA,OAAO,cAA0E;AAC/E,uBAAa,QAAQ,CAAC,EAAE,MAAM,MAAM,MAAM;AACxC,oBAAQ,QAAQ,IAAI,MAAM,KAAK;AAAA,UACjC,CAAC;AAED,qBAAW,aAAa,KAAK,EAAE,QAAQ,CAAC;AAExC,uBAAa,QAAQ,CAAC,EAAE,MAAM,OAAO,SAAAA,SAAQ,MAAM;AACjD,qBAAS,QAAQ,IAAI,MAAM,OAAOA,QAAO;AAAA,UAC3C,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,UAAM,eAAe,kBAAkB,QAAQ,QAAQ;AAGvD,QAAI,CAAC,cAAc;AACjB,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,MAAM;AACT,YAAM,WAAW,IAAI,IAAI,YAAY,QAAQ,GAAG;AAChD,eAAS,aAAa,IAAI,QAAQ,QAAQ;AAC1C,aAAO,aAAa,SAAS,QAAQ;AAAA,IACvC;AAGA,UAAM,WAAW,YAAY,MAAM,aAAa;AAEhD,QAAI,CAAC,gBAAgB,UAAU,aAAa,YAAY,GAAG;AAEzD,YAAM,eAAe,IAAI,IAAI,YAAY,QAAQ,GAAG;AACpD,mBAAa,aAAa,IAAI,SAAS,WAAW;AAClD,mBAAa,aAAa,IAAI,QAAQ,QAAQ;AAC9C,aAAO,aAAa,SAAS,YAAY;AAAA,IAC3C;AAGA,QAAI,eAAe;AACjB,YAAM,cAAc;AAAA,QAClB,IAAI,KAAK;AAAA,QACT,OAAO,KAAK,SAAS;AAAA,QACrB,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;AClMA,SAAS,sBAAAC,2BAA8C;AACvD,SAAS,eAAe;;;ACUjB,SAAS,gBAAgB,OAA2B;AAEzD,MAAI,iBAAiB,OAAO;AAC1B,UAAM,SAAS;AACf,WAAO;AAAA,MACL,SAAS,MAAM;AAAA,MACf,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,MACtD,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,IAC9D;AAAA,EACF;AAGA,MACE,OAAO,UAAU,YACjB,UAAU,QACV,aAAa,SACb,OAAQ,MAAkC,YAAY,UACtD;AACA,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAAA,MAChD,QAAQ,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAAA,IACxD;AAAA,EACF;AAGA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AAGA,SAAO,EAAE,SAAS,4BAA4B;AAChD;;;ADTO,SAAS,aACd,IACsD;AACtD,SAAO,UAAU,SAAkD;AACjE,QAAI;AACF,YAAM,cAAc,QAAQ,IAAI;AAChC,YAAM,kBAAkB,QAAQ,IAAI;AAEpC,UAAI,CAAC,eAAe,CAAC,iBAAiB;AACpC,eAAO;AAAA,UACL,MAAM;AAAA,UACN,OAAO;AAAA,YACL,SACE;AAAA,YACF,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,cAAc,MAAM,QAAQ;AAElC,YAAM,WAAWC,oBAAmB,aAAa,iBAAiB;AAAA,QAChE,SAAS;AAAA,UACP,SAAS;AACP,mBAAO,YAAY,OAAO;AAAA,UAC5B;AAAA,UACA,OAAO,cAA0E;AAC/E,gBAAI;AACF,2BAAa,QAAQ,CAAC,EAAE,MAAM,OAAO,QAAQ,MAAM;AACjD,4BAAY,IAAI,MAAM,OAAO,OAAO;AAAA,cACtC,CAAC;AAAA,YACH,QAAQ;AAAA,YAIR;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAED,YAAM,OAAO,MAAM,GAAG,UAAU,GAAG,IAAI;AAEvC,aAAO,EAAE,MAAM,OAAO,KAAK;AAAA,IAC7B,SAAS,QAAiB;AACxB,YAAM,QAAmB,gBAAgB,MAAM;AAC/C,aAAO,EAAE,MAAM,MAAM,MAAM;AAAA,IAC7B;AAAA,EACF;AACF;","names":["options","createServerClient","createServerClient"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/middleware/withSupaAuth.ts","../../src/server/actions/actionWrapper.ts","../../src/shared/utils/error-handler.ts","../../src/server/actions/routeWrapper.ts"],"sourcesContent":["import { createServerClient, type CookieOptions } from \"@supabase/ssr\";\nimport { NextResponse, type NextRequest } from \"next/server\";\n\nimport type { MiddlewareOptions, RouteConfig } from \"../../types\";\n\n// ── Path matching utility ───────────────────────────────────────────\n\n/**\n * Converts a route pattern into a RegExp for matching.\n *\n * Supports:\n * - Exact: `\"/settings\"` → matches only `/settings`\n * - Prefix: `\"/dashboard\"` → matches `/dashboard`, `/dashboard/stats`\n * - Wildcard: `\"/admin/:path*\"` → matches `/admin`, `/admin/users`, `/admin/a/b/c`\n */\nfunction matchPath(pattern: string, pathname: string): boolean {\n // Wildcard pattern: \"/admin/:path*\" → match \"/admin\" and everything below\n if (pattern.includes(\":path*\")) {\n const prefix = pattern.replace(/:path\\*$/, \"\").replace(/\\/$/, \"\");\n return pathname === prefix || pathname.startsWith(prefix + \"/\");\n }\n\n // Prefix matching: \"/dashboard\" matches \"/dashboard/anything\"\n return pathname === pattern || pathname.startsWith(pattern + \"/\");\n}\n\n/**\n * Finds the first `RouteConfig` whose pattern matches `pathname`, or `undefined`.\n */\nfunction findMatchingRoute(\n routes: RouteConfig[],\n pathname: string,\n): RouteConfig | undefined {\n return routes.find((route) => matchPath(route.path, pathname));\n}\n\n// ── Role extraction utility ─────────────────────────────────────────\n\ntype UserMeta = {\n user_metadata: Record<string, unknown>;\n app_metadata: Record<string, unknown>;\n};\n\nfunction extractRole(\n user: UserMeta,\n extractor: MiddlewareOptions[\"roleExtractor\"],\n): string | string[] | undefined {\n if (typeof extractor === \"function\") {\n return extractor(user);\n }\n\n const source =\n extractor === \"app_metadata\" ? user.app_metadata : user.user_metadata;\n\n const raw = source?.role;\n\n if (typeof raw === \"string\") return raw;\n if (Array.isArray(raw) && raw.every((r) => typeof r === \"string\")) {\n return raw as string[];\n }\n\n return undefined;\n}\n\n/**\n * Check whether the user's role(s) satisfy the route's `allowedRoles`.\n */\nfunction hasRequiredRole(\n userRole: string | string[] | undefined,\n allowedRoles: string[] | undefined,\n): boolean {\n // No role restriction → any authenticated user is allowed.\n if (!allowedRoles || allowedRoles.length === 0) return true;\n\n if (!userRole) return false;\n\n const roles = Array.isArray(userRole) ? userRole : [userRole];\n return roles.some((r) => allowedRoles.includes(r));\n}\n\n// ── Main middleware factory ─────────────────────────────────────────\n\n/**\n * Create a Next.js middleware handler that protects routes with\n * authentication **and** optional role-based access control (RBAC).\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { withSupaAuth } from \"next-supa-utils/server\";\n *\n * export default withSupaAuth({\n * routes: [\n * { path: \"/dashboard\" }, // any authed user\n * { path: \"/admin/:path*\", allowedRoles: [\"admin\"] }, // admin only\n * { path: \"/editor/:path*\", allowedRoles: [\"admin\", \"editor\"] },\n * ],\n * redirectTo: \"/login\",\n * roleExtractor: \"user_metadata\", // or \"app_metadata\" or a custom fn\n * });\n *\n * export const config = {\n * matcher: [\"/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)\"],\n * };\n * ```\n */\nexport function withSupaAuth(options: MiddlewareOptions) {\n const {\n routes,\n redirectTo = \"/login\",\n roleExtractor = \"user_metadata\",\n onAuthSuccess,\n } = options;\n\n return async function middleware(request: NextRequest): Promise<NextResponse> {\n // ── 1. Create a mutable response so Supabase can set cookies ──\n let response = NextResponse.next({\n request: { headers: request.headers },\n });\n\n const supabaseUrl = options.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = options.supabaseAnonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n console.error(\n \"[next-supa-utils] Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n );\n return response;\n }\n\n // ── 2. Initialize server client with middleware cookie helpers ─\n const supabase = createServerClient(supabaseUrl, supabaseAnonKey, {\n cookies: {\n getAll() {\n return request.cookies.getAll();\n },\n setAll(cookiesToSet: { name: string; value: string; options?: CookieOptions }[]) {\n cookiesToSet.forEach(({ name, value }) => {\n request.cookies.set(name, value);\n });\n\n response = NextResponse.next({ request });\n\n cookiesToSet.forEach(({ name, value, options }) => {\n response.cookies.set(name, value, options);\n });\n },\n },\n });\n\n // ── 3. Refresh session (required to keep tokens alive) ────────\n const {\n data: { user },\n } = await supabase.auth.getUser();\n\n const { pathname } = request.nextUrl;\n\n // ── 4. Find the matching route config ─────────────────────────\n const matchedRoute = findMatchingRoute(routes, pathname);\n\n // No matching route → not a protected path, pass through.\n if (!matchedRoute) {\n return response;\n }\n\n // ── 5. Check authentication ───────────────────────────────────\n if (!user) {\n const loginUrl = new URL(redirectTo, request.url);\n loginUrl.searchParams.set(\"next\", pathname);\n return NextResponse.redirect(loginUrl);\n }\n\n // ── 6. Check role-based access ────────────────────────────────\n const userRole = extractRole(user, roleExtractor);\n\n if (!hasRequiredRole(userRole, matchedRoute.allowedRoles)) {\n // User is logged in but lacks the required role.\n const forbiddenUrl = new URL(redirectTo, request.url);\n forbiddenUrl.searchParams.set(\"error\", \"forbidden\");\n forbiddenUrl.searchParams.set(\"next\", pathname);\n return NextResponse.redirect(forbiddenUrl);\n }\n\n // ── 7. Success callback ───────────────────────────────────────\n if (onAuthSuccess) {\n await onAuthSuccess({\n id: user.id,\n email: user.email ?? undefined,\n role: userRole,\n });\n }\n\n return response;\n };\n}\n","import { createServerClient, type CookieOptions } from \"@supabase/ssr\";\nimport { cookies } from \"next/headers\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\nimport type { ActionResponse, SupaError, ServerActionOptions } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\n\n/**\n * Create a type-safe Server Action that automatically:\n * 1. Initialises a Supabase server client (with cookies)\n * 2. Wraps execution in try/catch\n * 3. Returns a standardised `{ data, error }` response\n *\n * @example\n * ```ts\n * // app/actions/profile.ts\n * \"use server\";\n * import { createAction } from \"next-supa-utils/server\";\n *\n * export const getProfile = createAction(async (supabase, userId: string) => {\n * const { data, error } = await supabase\n * .from(\"profiles\")\n * .select(\"*\")\n * .eq(\"id\", userId)\n * .single();\n *\n * if (error) throw error;\n * return data;\n * });\n *\n * // Usage in a Server Component or Client Component:\n * const result = await getProfile(\"user-uuid\");\n * if (result.error) { ... }\n * ```\n */\nexport function createAction<TArgs extends unknown[], TResult>(\n fn: (supabase: SupabaseClient, ...args: TArgs) => Promise<TResult>,\n options?: ServerActionOptions,\n): (...args: TArgs) => Promise<ActionResponse<TResult>> {\n return async (...args: TArgs): Promise<ActionResponse<TResult>> => {\n try {\n const supabaseUrl = options?.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = options?.supabaseAnonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n return {\n data: null,\n error: {\n message:\n \"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n code: \"CONFIG_ERROR\",\n },\n };\n }\n\n const cookieStore = await cookies();\n\n const supabase = createServerClient(supabaseUrl, supabaseAnonKey, {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: { name: string; value: string; options?: CookieOptions }[]) {\n try {\n cookiesToSet.forEach(({ name, value, options }) => {\n cookieStore.set(name, value, options);\n });\n } catch {\n // `cookies().set()` throws when called from a Server Component.\n // In that context we only need read access — the middleware\n // handles token refresh.\n }\n },\n },\n });\n\n const data = await fn(supabase, ...args);\n\n return { data, error: null };\n } catch (caught: unknown) {\n const error: SupaError = handleSupaError(caught);\n return { data: null, error };\n }\n };\n}\n","import type { SupaError } from \"../../types\";\n\n/**\n * Normalize any thrown value into a consistent `SupaError` shape.\n *\n * Handles:\n * - Supabase `AuthError` / `PostgrestError` (has `.message` and optional `.code` / `.status`)\n * - Standard `Error` instances\n * - Plain strings\n * - Unknown values (fallback)\n */\nexport function handleSupaError(error: unknown): SupaError {\n // ── Supabase errors & standard Error instances ──────────────────\n if (error instanceof Error) {\n const record = error as unknown as Record<string, unknown>;\n return {\n message: error.message,\n code: typeof record.code === \"string\" ? record.code : undefined,\n status: typeof record.status === \"number\" ? record.status : undefined,\n };\n }\n\n // ── Plain object with a message property ────────────────────────\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof (error as Record<string, unknown>).message === \"string\"\n ) {\n const err = error as Record<string, unknown>;\n return {\n message: err.message as string,\n code: typeof err.code === \"string\" ? err.code : undefined,\n status: typeof err.status === \"number\" ? err.status : undefined,\n };\n }\n\n // ── String ──────────────────────────────────────────────────────\n if (typeof error === \"string\") {\n return { message: error };\n }\n\n // ── Fallback ────────────────────────────────────────────────────\n return { message: \"An unknown error occurred\" };\n}\n","import { createServerClient, type CookieOptions } from \"@supabase/ssr\";\nimport { NextResponse, type NextRequest } from \"next/server\";\nimport { cookies } from \"next/headers\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\nimport type { RouteWrapperOptions, SupaError } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\n\n/**\n * Higher-order function that wraps a Next.js App Router Route Handler\n * with standardized error handling and optional authentication gating.\n *\n * @example\n * ```ts\n * // app/api/posts/route.ts\n * import { routeWrapper } from \"next-supa-utils/server\";\n *\n * // Public endpoint (no auth required)\n * export const GET = routeWrapper(async (request) => {\n * const data = await fetchPosts();\n * return NextResponse.json({ data });\n * });\n *\n * // Protected endpoint (requires valid Supabase session)\n * export const POST = routeWrapper(\n * async (request, { supabase, user }) => {\n * const body = await request.json();\n * const { data, error } = await supabase\n * .from(\"posts\")\n * .insert({ ...body, user_id: user.id })\n * .select()\n * .single();\n *\n * if (error) throw error;\n * return NextResponse.json({ data }, { status: 201 });\n * },\n * { requireAuth: true },\n * );\n * ```\n */\nexport function routeWrapper<TContext extends Record<string, unknown> = Record<string, unknown>>(\n handler: (\n request: NextRequest,\n context: RouteHandlerContext<TContext>,\n ) => Promise<NextResponse | Response>,\n options?: RouteWrapperOptions,\n) {\n const { requireAuth = false, supabaseUrl: optUrl, supabaseAnonKey: optKey } = options ?? {};\n\n return async (\n request: NextRequest,\n routeContext?: { params?: Promise<TContext> },\n ): Promise<NextResponse> => {\n try {\n const supabaseUrl = optUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = optKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n return NextResponse.json(\n { error: { message: \"Server configuration error\", code: \"CONFIG_ERROR\" } },\n { status: 500 },\n );\n }\n\n // ── Build context to pass to the handler ────────────────────\n const ctx: RouteHandlerContext<TContext> = {\n params: routeContext?.params ? await routeContext.params : ({} as TContext),\n supabase: null as unknown as SupabaseClient,\n user: null,\n };\n\n // ── Optionally init Supabase & check auth ───────────────────\n if (requireAuth) {\n const cookieStore = await cookies();\n\n const supabase = createServerClient(supabaseUrl, supabaseAnonKey, {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: { name: string; value: string; options?: CookieOptions }[]) {\n try {\n cookiesToSet.forEach(({ name, value, options: opts }) => {\n cookieStore.set(name, value, opts);\n });\n } catch {\n // cookies().set() may throw in read-only contexts\n }\n },\n },\n });\n\n const {\n data: { user },\n error: authError,\n } = await supabase.auth.getUser();\n\n if (authError || !user) {\n return NextResponse.json(\n {\n error: {\n message: \"Unauthorized: valid session required\",\n code: \"UNAUTHORIZED\",\n } satisfies SupaError,\n },\n { status: 401 },\n );\n }\n\n ctx.supabase = supabase;\n ctx.user = user;\n } else {\n // Even for public routes, provide a Supabase client for convenience.\n const cookieStore = await cookies();\n\n ctx.supabase = createServerClient(supabaseUrl, supabaseAnonKey, {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: { name: string; value: string; options?: CookieOptions }[]) {\n try {\n cookiesToSet.forEach(({ name, value, options: opts }) => {\n cookieStore.set(name, value, opts);\n });\n } catch {\n // cookies().set() may throw in read-only contexts\n }\n },\n },\n });\n }\n\n // ── Execute the handler ─────────────────────────────────────\n const response = await handler(request, ctx);\n return response instanceof NextResponse\n ? response\n : NextResponse.json(await response.json(), { status: response.status });\n } catch (caught: unknown) {\n const error = handleSupaError(caught);\n const status = error.status ?? 500;\n\n return NextResponse.json({ error }, { status });\n }\n };\n}\n\n// ── Internal context type ───────────────────────────────────────────\n\n/** Context object passed to the route handler by `routeWrapper`. */\nexport interface RouteHandlerContext<\n TParams extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Resolved dynamic route params (e.g. `{ id: \"123\" }`). */\n params: TParams;\n\n /**\n * Supabase server client.\n * Always available (even on public routes) for convenience queries.\n */\n supabase: SupabaseClient;\n\n /**\n * The authenticated user, or `null` for public (non-auth) routes.\n * Guaranteed non-null when `requireAuth: true`.\n */\n user: Awaited<ReturnType<SupabaseClient[\"auth\"][\"getUser\"]>>[\"data\"][\"user\"];\n}\n"],"mappings":"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","names":["options","createServerClient","createServerClient","options","createServerClient","NextResponse","cookies","NextResponse","cookies","createServerClient"]}
|
package/dist/shared/index.d.cts
CHANGED
|
@@ -56,6 +56,16 @@ interface MiddlewareOptions {
|
|
|
56
56
|
* @default "/login"
|
|
57
57
|
*/
|
|
58
58
|
redirectTo?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Explicit Supabase Project URL.
|
|
61
|
+
* If omitted, falls back to `process.env.NEXT_PUBLIC_SUPABASE_URL`.
|
|
62
|
+
*/
|
|
63
|
+
supabaseUrl?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Explicit Supabase Anon Key.
|
|
66
|
+
* If omitted, falls back to `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`.
|
|
67
|
+
*/
|
|
68
|
+
supabaseAnonKey?: string;
|
|
59
69
|
/**
|
|
60
70
|
* Strategy for extracting the user's role from the Supabase user object.
|
|
61
71
|
*
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -56,6 +56,16 @@ interface MiddlewareOptions {
|
|
|
56
56
|
* @default "/login"
|
|
57
57
|
*/
|
|
58
58
|
redirectTo?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Explicit Supabase Project URL.
|
|
61
|
+
* If omitted, falls back to `process.env.NEXT_PUBLIC_SUPABASE_URL`.
|
|
62
|
+
*/
|
|
63
|
+
supabaseUrl?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Explicit Supabase Anon Key.
|
|
66
|
+
* If omitted, falls back to `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`.
|
|
67
|
+
*/
|
|
68
|
+
supabaseAnonKey?: string;
|
|
59
69
|
/**
|
|
60
70
|
* Strategy for extracting the user's role from the Supabase user object.
|
|
61
71
|
*
|
package/package.json
CHANGED