@stackwright-pro/auth-nextjs 0.2.0-alpha.4 → 0.2.0-alpha.7

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 ADDED
@@ -0,0 +1,21 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-2026 Per Aspera LLC. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary and confidential property of Per Aspera LLC ("Company").
7
+
8
+ RESTRICTIONS: You may not use, copy, modify, merge, publish, distribute,
9
+ sublicense, sell, or otherwise exploit this Software or any portion thereof
10
+ without the express prior written consent of the Company.
11
+
12
+ GOVERNMENT USE: Use, duplication, or disclosure by the U.S. Government is
13
+ subject to restrictions as set forth in FAR 52.227-19 (Commercial Computer
14
+ Software - Restricted Rights) and DFARS 252.227-7013 (Rights in Technical
15
+ Data and Computer Software), as applicable.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED. IN NO EVENT SHALL THE COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES, OR
19
+ OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
20
+
21
+ For licensing inquiries: legal@peraspera.com
package/dist/index.d.mts CHANGED
@@ -107,4 +107,101 @@ declare function setSessionCookie(res: NextApiResponse, sessionJwt: string, opti
107
107
  */
108
108
  declare function clearSessionCookie(res: NextApiResponse, options?: Pick<CookieOptions, 'name' | 'domain' | 'path'>): void;
109
109
 
110
- export { type AuthenticatedHandler, type MiddlewareConfig, type ProtectedRouteConfig, clearSessionCookie, createAuthMiddleware, protectedRoute, setSessionCookie };
110
+ interface RouteHandlerConfig {
111
+ /**
112
+ * Session manager for validating sessions
113
+ */
114
+ sessionManager: SessionManager;
115
+ /**
116
+ * Required roles (user must have ANY of these)
117
+ */
118
+ roles?: string[];
119
+ /**
120
+ * Required permissions (user must have ALL of these)
121
+ */
122
+ permissions?: string[];
123
+ /**
124
+ * Cookie name (default: 'stackwright_session')
125
+ */
126
+ cookieName?: string;
127
+ /**
128
+ * Optional RBAC engine for wildcard-aware role/permission checking.
129
+ * When provided, authorization decisions use RBACEngine (consistent
130
+ * with middleware). Without it, falls back to direct array checks.
131
+ */
132
+ rbacEngine?: RBACEngine;
133
+ }
134
+ /**
135
+ * Handler function with authenticated session — App Router Route Handler signature.
136
+ */
137
+ type AuthenticatedRouteHandler = (request: NextRequest, context: {
138
+ params: Record<string, string>;
139
+ }, session: AuthSession) => Promise<Response> | Response;
140
+ /**
141
+ * Wrap an App Router Route Handler with authentication/authorization.
142
+ *
143
+ * This is the App Router equivalent of `protectedRoute()` from `api-helpers.ts`.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * // app/api/equipment/[id]/route.ts
148
+ * import { protectedRouteHandler } from '@stackwright-pro/auth-nextjs';
149
+ *
150
+ * export const GET = protectedRouteHandler(
151
+ * async (request, { params }, session) => {
152
+ * const equipment = await getEquipment(params.id);
153
+ * return NextResponse.json(equipment);
154
+ * },
155
+ * {
156
+ * sessionManager,
157
+ * roles: ['ANALYST', 'ADMIN'],
158
+ * }
159
+ * );
160
+ * ```
161
+ */
162
+ declare function protectedRouteHandler(handler: AuthenticatedRouteHandler, config: RouteHandlerConfig): (request: NextRequest, context: {
163
+ params: Record<string, string>;
164
+ }) => Promise<Response>;
165
+ /**
166
+ * Set the session cookie on a `NextResponse` (App Router).
167
+ *
168
+ * This is the App Router equivalent of `setSessionCookie()` from `cookie-utils.ts`.
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * // app/api/auth/login/route.ts
173
+ * import { setSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
174
+ *
175
+ * export async function POST(request: NextRequest) {
176
+ * const session = await createSession(request);
177
+ * const jwt = await sessionManager.serialize(session);
178
+ * const response = NextResponse.json({ ok: true });
179
+ * return setSessionCookieOnResponse(response, jwt);
180
+ * }
181
+ * ```
182
+ */
183
+ declare function setSessionCookieOnResponse(response: NextResponse, sessionJwt: string, options?: {
184
+ cookieName?: string;
185
+ maxAge?: number;
186
+ }): NextResponse;
187
+ /**
188
+ * Clear the session cookie on a `NextResponse` (App Router).
189
+ *
190
+ * This is the App Router equivalent of `clearSessionCookie()` from `cookie-utils.ts`.
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * // app/api/auth/logout/route.ts
195
+ * import { clearSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
196
+ *
197
+ * export async function POST() {
198
+ * const response = NextResponse.json({ ok: true });
199
+ * return clearSessionCookieOnResponse(response);
200
+ * }
201
+ * ```
202
+ */
203
+ declare function clearSessionCookieOnResponse(response: NextResponse, options?: {
204
+ cookieName?: string;
205
+ }): NextResponse;
206
+
207
+ export { type AuthenticatedHandler, type AuthenticatedRouteHandler, type MiddlewareConfig, type ProtectedRouteConfig, type RouteHandlerConfig, clearSessionCookie, clearSessionCookieOnResponse, createAuthMiddleware, protectedRoute, protectedRouteHandler, setSessionCookie, setSessionCookieOnResponse };
package/dist/index.d.ts CHANGED
@@ -107,4 +107,101 @@ declare function setSessionCookie(res: NextApiResponse, sessionJwt: string, opti
107
107
  */
108
108
  declare function clearSessionCookie(res: NextApiResponse, options?: Pick<CookieOptions, 'name' | 'domain' | 'path'>): void;
109
109
 
110
- export { type AuthenticatedHandler, type MiddlewareConfig, type ProtectedRouteConfig, clearSessionCookie, createAuthMiddleware, protectedRoute, setSessionCookie };
110
+ interface RouteHandlerConfig {
111
+ /**
112
+ * Session manager for validating sessions
113
+ */
114
+ sessionManager: SessionManager;
115
+ /**
116
+ * Required roles (user must have ANY of these)
117
+ */
118
+ roles?: string[];
119
+ /**
120
+ * Required permissions (user must have ALL of these)
121
+ */
122
+ permissions?: string[];
123
+ /**
124
+ * Cookie name (default: 'stackwright_session')
125
+ */
126
+ cookieName?: string;
127
+ /**
128
+ * Optional RBAC engine for wildcard-aware role/permission checking.
129
+ * When provided, authorization decisions use RBACEngine (consistent
130
+ * with middleware). Without it, falls back to direct array checks.
131
+ */
132
+ rbacEngine?: RBACEngine;
133
+ }
134
+ /**
135
+ * Handler function with authenticated session — App Router Route Handler signature.
136
+ */
137
+ type AuthenticatedRouteHandler = (request: NextRequest, context: {
138
+ params: Record<string, string>;
139
+ }, session: AuthSession) => Promise<Response> | Response;
140
+ /**
141
+ * Wrap an App Router Route Handler with authentication/authorization.
142
+ *
143
+ * This is the App Router equivalent of `protectedRoute()` from `api-helpers.ts`.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * // app/api/equipment/[id]/route.ts
148
+ * import { protectedRouteHandler } from '@stackwright-pro/auth-nextjs';
149
+ *
150
+ * export const GET = protectedRouteHandler(
151
+ * async (request, { params }, session) => {
152
+ * const equipment = await getEquipment(params.id);
153
+ * return NextResponse.json(equipment);
154
+ * },
155
+ * {
156
+ * sessionManager,
157
+ * roles: ['ANALYST', 'ADMIN'],
158
+ * }
159
+ * );
160
+ * ```
161
+ */
162
+ declare function protectedRouteHandler(handler: AuthenticatedRouteHandler, config: RouteHandlerConfig): (request: NextRequest, context: {
163
+ params: Record<string, string>;
164
+ }) => Promise<Response>;
165
+ /**
166
+ * Set the session cookie on a `NextResponse` (App Router).
167
+ *
168
+ * This is the App Router equivalent of `setSessionCookie()` from `cookie-utils.ts`.
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * // app/api/auth/login/route.ts
173
+ * import { setSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
174
+ *
175
+ * export async function POST(request: NextRequest) {
176
+ * const session = await createSession(request);
177
+ * const jwt = await sessionManager.serialize(session);
178
+ * const response = NextResponse.json({ ok: true });
179
+ * return setSessionCookieOnResponse(response, jwt);
180
+ * }
181
+ * ```
182
+ */
183
+ declare function setSessionCookieOnResponse(response: NextResponse, sessionJwt: string, options?: {
184
+ cookieName?: string;
185
+ maxAge?: number;
186
+ }): NextResponse;
187
+ /**
188
+ * Clear the session cookie on a `NextResponse` (App Router).
189
+ *
190
+ * This is the App Router equivalent of `clearSessionCookie()` from `cookie-utils.ts`.
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * // app/api/auth/logout/route.ts
195
+ * import { clearSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
196
+ *
197
+ * export async function POST() {
198
+ * const response = NextResponse.json({ ok: true });
199
+ * return clearSessionCookieOnResponse(response);
200
+ * }
201
+ * ```
202
+ */
203
+ declare function clearSessionCookieOnResponse(response: NextResponse, options?: {
204
+ cookieName?: string;
205
+ }): NextResponse;
206
+
207
+ export { type AuthenticatedHandler, type AuthenticatedRouteHandler, type MiddlewareConfig, type ProtectedRouteConfig, type RouteHandlerConfig, clearSessionCookie, clearSessionCookieOnResponse, createAuthMiddleware, protectedRoute, protectedRouteHandler, setSessionCookie, setSessionCookieOnResponse };
package/dist/index.js CHANGED
@@ -24,9 +24,12 @@ __export(index_exports, {
24
24
  AuthProvider: () => import_auth3.AuthProvider,
25
25
  RBACEngine: () => import_auth2.RBACEngine,
26
26
  clearSessionCookie: () => clearSessionCookie,
27
+ clearSessionCookieOnResponse: () => clearSessionCookieOnResponse,
27
28
  createAuthMiddleware: () => createAuthMiddleware,
28
29
  protectedRoute: () => protectedRoute,
30
+ protectedRouteHandler: () => protectedRouteHandler,
29
31
  setSessionCookie: () => setSessionCookie,
32
+ setSessionCookieOnResponse: () => setSessionCookieOnResponse,
30
33
  useAuth: () => import_auth3.useAuth,
31
34
  useRequireAuth: () => import_auth3.useRequireAuth
32
35
  });
@@ -151,15 +154,81 @@ function clearSessionCookie(res, options = {}) {
151
154
  // src/index.ts
152
155
  var import_auth2 = require("@stackwright-pro/auth");
153
156
  var import_auth3 = require("@stackwright-pro/auth");
157
+
158
+ // src/route-handlers.ts
159
+ var import_server2 = require("next/server");
160
+ function protectedRouteHandler(handler, config) {
161
+ const {
162
+ sessionManager,
163
+ roles,
164
+ permissions,
165
+ cookieName = "stackwright_session",
166
+ rbacEngine
167
+ } = config;
168
+ return async (request, context) => {
169
+ const sessionCookie = request.cookies.get(cookieName);
170
+ if (!sessionCookie) {
171
+ return import_server2.NextResponse.json({ error: "Not authenticated" }, { status: 401 });
172
+ }
173
+ const session = await sessionManager.deserialize(sessionCookie.value);
174
+ if (!session || sessionManager.isExpired(session)) {
175
+ return import_server2.NextResponse.json({ error: "Session expired" }, { status: 401 });
176
+ }
177
+ if (roles && roles.length > 0) {
178
+ if (rbacEngine) {
179
+ if (!rbacEngine.hasAnyRole(session.user, roles)) {
180
+ return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
181
+ }
182
+ } else {
183
+ const hasRole = roles.some((role) => session.user.roles.includes(role));
184
+ if (!hasRole) {
185
+ return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
186
+ }
187
+ }
188
+ }
189
+ if (permissions && permissions.length > 0) {
190
+ if (rbacEngine) {
191
+ if (!rbacEngine.hasAllPermissions(session.user, permissions)) {
192
+ return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
193
+ }
194
+ } else {
195
+ const hasAllPermissions = permissions.every((p) => session.user.permissions?.includes(p));
196
+ if (!hasAllPermissions) {
197
+ return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
198
+ }
199
+ }
200
+ }
201
+ return handler(request, context, session);
202
+ };
203
+ }
204
+ function setSessionCookieOnResponse(response, sessionJwt, options = {}) {
205
+ const cookieName = options.cookieName ?? "stackwright_session";
206
+ const maxAge = options.maxAge ?? 900;
207
+ response.cookies.set(cookieName, sessionJwt, {
208
+ httpOnly: true,
209
+ secure: true,
210
+ sameSite: "lax",
211
+ maxAge
212
+ });
213
+ return response;
214
+ }
215
+ function clearSessionCookieOnResponse(response, options = {}) {
216
+ const cookieName = options.cookieName ?? "stackwright_session";
217
+ response.cookies.delete(cookieName);
218
+ return response;
219
+ }
154
220
  // Annotate the CommonJS export names for ESM import in node:
155
221
  0 && (module.exports = {
156
222
  AuthContext,
157
223
  AuthProvider,
158
224
  RBACEngine,
159
225
  clearSessionCookie,
226
+ clearSessionCookieOnResponse,
160
227
  createAuthMiddleware,
161
228
  protectedRoute,
229
+ protectedRouteHandler,
162
230
  setSessionCookie,
231
+ setSessionCookieOnResponse,
163
232
  useAuth,
164
233
  useRequireAuth
165
234
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/api-helpers.ts","../src/cookie-utils.ts"],"sourcesContent":["export { createAuthMiddleware, type MiddlewareConfig } from './middleware.js';\nexport {\n protectedRoute,\n type ProtectedRouteConfig,\n type AuthenticatedHandler,\n} from './api-helpers.js';\nexport { setSessionCookie, clearSessionCookie } from './cookie-utils.js';\n\n// Re-export commonly used types/classes from core\nexport type { AuthUser, AuthSession, AuthConfig } from '@stackwright-pro/auth';\n\n// Re-export RBAC engine\nexport { RBACEngine } from '@stackwright-pro/auth';\n\n// Re-export React context components for Next.js apps\nexport { AuthProvider, AuthContext, useAuth, useRequireAuth } from '@stackwright-pro/auth';\nexport type { AuthProviderProps, AuthContextValue } from '@stackwright-pro/auth';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n SessionManager,\n RBACEngine,\n type AuthConfig,\n type AuthSession,\n} from '@stackwright-pro/auth';\n\nexport interface MiddlewareConfig {\n /**\n * Auth configuration\n */\n authConfig: AuthConfig;\n\n /**\n * Session manager instance\n */\n sessionManager: SessionManager;\n\n /**\n * RBAC engine instance\n */\n rbacEngine: RBACEngine;\n\n /**\n * Cookie name for session (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * URL to redirect to when authentication fails (default: '/login')\n */\n loginUrl?: string;\n\n /**\n * URL to redirect to when authorization fails (default: '/unauthorized')\n */\n unauthorizedUrl?: string;\n}\n\n/**\n * Create Next.js middleware for authentication and authorization\n *\n * @example\n * ```typescript\n * // middleware.ts\n * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthMiddleware({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthMiddleware(config: MiddlewareConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authMiddleware(request: NextRequest): Promise<NextResponse | undefined> {\n const { pathname } = request.nextUrl;\n\n // Check if route is public\n if (rbacEngine.isPublicRoute(pathname)) {\n return undefined; // Continue to next middleware\n }\n\n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n let session: AuthSession | null = null;\n\n if (sessionCookie) {\n session = await sessionManager.deserialize(sessionCookie.value);\n }\n\n // Check if session is valid\n if (!session || sessionManager.isExpired(session)) {\n // Redirect to login\n const url = request.nextUrl.clone();\n url.pathname = loginUrl;\n url.searchParams.set('redirect', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if user can access route\n if (!rbacEngine.canAccessRoute(session.user, pathname)) {\n // Redirect to unauthorized\n const url = request.nextUrl.clone();\n url.pathname = unauthorizedUrl;\n return NextResponse.redirect(url);\n }\n\n // Refresh session if needed\n if (sessionManager.shouldRefresh(session)) {\n const refreshed = await sessionManager.refreshSession(session);\n const newJwt = await sessionManager.serialize(refreshed);\n\n const response = NextResponse.next();\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n maxAge: 900, // 15 minutes\n });\n\n return response;\n }\n\n // Allow request to continue\n return undefined;\n };\n}\n","import type { NextApiRequest, NextApiResponse } from 'next';\nimport { SessionManager, RBACEngine, type AuthSession, type AuthUser } from '@stackwright-pro/auth';\n\nexport interface ProtectedRouteConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n\n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n\n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n\n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * Optional RBAC engine for wildcard-aware role/permission checking.\n * When provided, authorization decisions use RBACEngine (consistent\n * with middleware). Without it, falls back to direct array checks.\n */\n rbacEngine?: RBACEngine;\n}\n\n/**\n * Handler function with authenticated session\n */\nexport type AuthenticatedHandler<T = any> = (\n req: NextApiRequest,\n res: NextApiResponse<T>,\n session: AuthSession\n) => void | Promise<void>;\n\n/**\n * Wrap API route with authentication/authorization\n *\n * @example\n * ```typescript\n * // pages/api/equipment/[id].ts\n * export default protectedRoute(\n * async (req, res, session) => {\n * const equipment = await getEquipment(req.query.id);\n * res.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRoute<T = any>(\n handler: AuthenticatedHandler<T>,\n config: ProtectedRouteConfig\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const { sessionManager, roles, permissions, cookieName = 'stackwright_session' } = config;\n\n return async (req: NextApiRequest, res: NextApiResponse) => {\n // Extract session from cookie\n const sessionCookie = req.cookies[cookieName];\n\n if (!sessionCookie) {\n res.status(401).json({ error: 'Not authenticated' } as any);\n return;\n }\n\n // Verify session\n const session = await sessionManager.deserialize(sessionCookie);\n\n if (!session || sessionManager.isExpired(session)) {\n res.status(401).json({ error: 'Session expired' } as any);\n return;\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for consistent wildcard-aware checking\n if (!config.rbacEngine.hasAnyRole(session.user, roles)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasRole = roles.some((role) => session.user.roles.includes(role));\n if (!hasRole) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for wildcard-aware permission checking\n if (!config.rbacEngine.hasAllPermissions(session.user, permissions)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasAllPermissions = permissions.every((permission) =>\n session.user.permissions?.includes(permission)\n );\n if (!hasAllPermissions) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Call handler with session\n await handler(req, res, session);\n };\n}\n","import type { NextApiResponse } from 'next';\nimport { serializeCookie, clearCookie, type CookieOptions } from '@stackwright-pro/auth';\n\n/**\n * Set session cookie in Next.js API response\n */\nexport function setSessionCookie(\n res: NextApiResponse,\n sessionJwt: string,\n options: CookieOptions = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n const maxAge = options.maxAge || 900; // 15 minutes default\n\n const cookie = serializeCookie(cookieName, sessionJwt, {\n ...options,\n maxAge,\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n });\n\n res.setHeader('Set-Cookie', cookie);\n}\n\n/**\n * Clear session cookie in Next.js API response\n */\nexport function clearSessionCookie(\n res: NextApiResponse,\n options: Pick<CookieOptions, 'name' | 'domain' | 'path'> = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n\n const cookie = clearCookie(cookieName, options);\n\n res.setHeader('Set-Cookie', cookie);\n}\n"],"mappings":"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","names":["import_auth"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/api-helpers.ts","../src/cookie-utils.ts","../src/route-handlers.ts"],"sourcesContent":["export { createAuthMiddleware, type MiddlewareConfig } from './middleware.js';\nexport {\n protectedRoute,\n type ProtectedRouteConfig,\n type AuthenticatedHandler,\n} from './api-helpers.js';\nexport { setSessionCookie, clearSessionCookie } from './cookie-utils.js';\n\n// Re-export commonly used types/classes from core\nexport type { AuthUser, AuthSession, AuthConfig } from '@stackwright-pro/auth';\n\n// Re-export RBAC engine\nexport { RBACEngine } from '@stackwright-pro/auth';\n\n// Re-export React context components for Next.js apps\nexport { AuthProvider, AuthContext, useAuth, useRequireAuth } from '@stackwright-pro/auth';\nexport type { AuthProviderProps, AuthContextValue } from '@stackwright-pro/auth';\n\n// App Router Route Handler helpers (replaces api-helpers for App Router users)\nexport {\n protectedRouteHandler,\n setSessionCookieOnResponse,\n clearSessionCookieOnResponse,\n type RouteHandlerConfig,\n type AuthenticatedRouteHandler,\n} from './route-handlers.js';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n SessionManager,\n RBACEngine,\n type AuthConfig,\n type AuthSession,\n} from '@stackwright-pro/auth';\n\nexport interface MiddlewareConfig {\n /**\n * Auth configuration\n */\n authConfig: AuthConfig;\n\n /**\n * Session manager instance\n */\n sessionManager: SessionManager;\n\n /**\n * RBAC engine instance\n */\n rbacEngine: RBACEngine;\n\n /**\n * Cookie name for session (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * URL to redirect to when authentication fails (default: '/login')\n */\n loginUrl?: string;\n\n /**\n * URL to redirect to when authorization fails (default: '/unauthorized')\n */\n unauthorizedUrl?: string;\n}\n\n/**\n * Create Next.js middleware for authentication and authorization\n *\n * @example\n * ```typescript\n * // middleware.ts\n * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthMiddleware({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthMiddleware(config: MiddlewareConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authMiddleware(request: NextRequest): Promise<NextResponse | undefined> {\n const { pathname } = request.nextUrl;\n\n // Check if route is public\n if (rbacEngine.isPublicRoute(pathname)) {\n return undefined; // Continue to next middleware\n }\n\n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n let session: AuthSession | null = null;\n\n if (sessionCookie) {\n session = await sessionManager.deserialize(sessionCookie.value);\n }\n\n // Check if session is valid\n if (!session || sessionManager.isExpired(session)) {\n // Redirect to login\n const url = request.nextUrl.clone();\n url.pathname = loginUrl;\n url.searchParams.set('redirect', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if user can access route\n if (!rbacEngine.canAccessRoute(session.user, pathname)) {\n // Redirect to unauthorized\n const url = request.nextUrl.clone();\n url.pathname = unauthorizedUrl;\n return NextResponse.redirect(url);\n }\n\n // Refresh session if needed\n if (sessionManager.shouldRefresh(session)) {\n const refreshed = await sessionManager.refreshSession(session);\n const newJwt = await sessionManager.serialize(refreshed);\n\n const response = NextResponse.next();\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n maxAge: 900, // 15 minutes\n });\n\n return response;\n }\n\n // Allow request to continue\n return undefined;\n };\n}\n","import type { NextApiRequest, NextApiResponse } from 'next';\nimport { SessionManager, RBACEngine, type AuthSession, type AuthUser } from '@stackwright-pro/auth';\n\nexport interface ProtectedRouteConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n\n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n\n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n\n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * Optional RBAC engine for wildcard-aware role/permission checking.\n * When provided, authorization decisions use RBACEngine (consistent\n * with middleware). Without it, falls back to direct array checks.\n */\n rbacEngine?: RBACEngine;\n}\n\n/**\n * Handler function with authenticated session\n */\nexport type AuthenticatedHandler<T = any> = (\n req: NextApiRequest,\n res: NextApiResponse<T>,\n session: AuthSession\n) => void | Promise<void>;\n\n/**\n * Wrap API route with authentication/authorization\n *\n * @example\n * ```typescript\n * // pages/api/equipment/[id].ts\n * export default protectedRoute(\n * async (req, res, session) => {\n * const equipment = await getEquipment(req.query.id);\n * res.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRoute<T = any>(\n handler: AuthenticatedHandler<T>,\n config: ProtectedRouteConfig\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const { sessionManager, roles, permissions, cookieName = 'stackwright_session' } = config;\n\n return async (req: NextApiRequest, res: NextApiResponse) => {\n // Extract session from cookie\n const sessionCookie = req.cookies[cookieName];\n\n if (!sessionCookie) {\n res.status(401).json({ error: 'Not authenticated' } as any);\n return;\n }\n\n // Verify session\n const session = await sessionManager.deserialize(sessionCookie);\n\n if (!session || sessionManager.isExpired(session)) {\n res.status(401).json({ error: 'Session expired' } as any);\n return;\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for consistent wildcard-aware checking\n if (!config.rbacEngine.hasAnyRole(session.user, roles)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasRole = roles.some((role) => session.user.roles.includes(role));\n if (!hasRole) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for wildcard-aware permission checking\n if (!config.rbacEngine.hasAllPermissions(session.user, permissions)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasAllPermissions = permissions.every((permission) =>\n session.user.permissions?.includes(permission)\n );\n if (!hasAllPermissions) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Call handler with session\n await handler(req, res, session);\n };\n}\n","import type { NextApiResponse } from 'next';\nimport { serializeCookie, clearCookie, type CookieOptions } from '@stackwright-pro/auth';\n\n/**\n * Set session cookie in Next.js API response\n */\nexport function setSessionCookie(\n res: NextApiResponse,\n sessionJwt: string,\n options: CookieOptions = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n const maxAge = options.maxAge || 900; // 15 minutes default\n\n const cookie = serializeCookie(cookieName, sessionJwt, {\n ...options,\n maxAge,\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n });\n\n res.setHeader('Set-Cookie', cookie);\n}\n\n/**\n * Clear session cookie in Next.js API response\n */\nexport function clearSessionCookie(\n res: NextApiResponse,\n options: Pick<CookieOptions, 'name' | 'domain' | 'path'> = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n\n const cookie = clearCookie(cookieName, options);\n\n res.setHeader('Set-Cookie', cookie);\n}\n","import { type NextRequest, NextResponse } from 'next/server';\nimport { SessionManager, RBACEngine, type AuthSession } from '@stackwright-pro/auth';\n\nexport interface RouteHandlerConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n\n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n\n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n\n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * Optional RBAC engine for wildcard-aware role/permission checking.\n * When provided, authorization decisions use RBACEngine (consistent\n * with middleware). Without it, falls back to direct array checks.\n */\n rbacEngine?: RBACEngine;\n}\n\n/**\n * Handler function with authenticated session — App Router Route Handler signature.\n */\nexport type AuthenticatedRouteHandler = (\n request: NextRequest,\n context: { params: Record<string, string> },\n session: AuthSession\n) => Promise<Response> | Response;\n\n/**\n * Wrap an App Router Route Handler with authentication/authorization.\n *\n * This is the App Router equivalent of `protectedRoute()` from `api-helpers.ts`.\n *\n * @example\n * ```typescript\n * // app/api/equipment/[id]/route.ts\n * import { protectedRouteHandler } from '@stackwright-pro/auth-nextjs';\n *\n * export const GET = protectedRouteHandler(\n * async (request, { params }, session) => {\n * const equipment = await getEquipment(params.id);\n * return NextResponse.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRouteHandler(\n handler: AuthenticatedRouteHandler,\n config: RouteHandlerConfig\n): (request: NextRequest, context: { params: Record<string, string> }) => Promise<Response> {\n const {\n sessionManager,\n roles,\n permissions,\n cookieName = 'stackwright_session',\n rbacEngine,\n } = config;\n\n return async (\n request: NextRequest,\n context: { params: Record<string, string> }\n ): Promise<Response> => {\n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n\n if (!sessionCookie) {\n return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });\n }\n\n // Verify session\n const session = await sessionManager.deserialize(sessionCookie.value);\n\n if (!session || sessionManager.isExpired(session)) {\n return NextResponse.json({ error: 'Session expired' }, { status: 401 });\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n if (rbacEngine) {\n if (!rbacEngine.hasAnyRole(session.user, roles)) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n } else {\n const hasRole = roles.some((role) => session.user.roles.includes(role));\n if (!hasRole) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n }\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n if (rbacEngine) {\n if (!rbacEngine.hasAllPermissions(session.user, permissions)) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n } else {\n const hasAllPermissions = permissions.every((p) => session.user.permissions?.includes(p));\n if (!hasAllPermissions) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n }\n }\n\n return handler(request, context, session);\n };\n}\n\n/**\n * Set the session cookie on a `NextResponse` (App Router).\n *\n * This is the App Router equivalent of `setSessionCookie()` from `cookie-utils.ts`.\n *\n * @example\n * ```typescript\n * // app/api/auth/login/route.ts\n * import { setSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';\n *\n * export async function POST(request: NextRequest) {\n * const session = await createSession(request);\n * const jwt = await sessionManager.serialize(session);\n * const response = NextResponse.json({ ok: true });\n * return setSessionCookieOnResponse(response, jwt);\n * }\n * ```\n */\nexport function setSessionCookieOnResponse(\n response: NextResponse,\n sessionJwt: string,\n options: {\n cookieName?: string;\n maxAge?: number;\n } = {}\n): NextResponse {\n const cookieName = options.cookieName ?? 'stackwright_session';\n const maxAge = options.maxAge ?? 900; // 15 minutes\n\n response.cookies.set(cookieName, sessionJwt, {\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n maxAge,\n });\n\n return response;\n}\n\n/**\n * Clear the session cookie on a `NextResponse` (App Router).\n *\n * This is the App Router equivalent of `clearSessionCookie()` from `cookie-utils.ts`.\n *\n * @example\n * ```typescript\n * // app/api/auth/logout/route.ts\n * import { clearSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';\n *\n * export async function POST() {\n * const response = NextResponse.json({ ok: true });\n * return clearSessionCookieOnResponse(response);\n * }\n * ```\n */\nexport function clearSessionCookieOnResponse(\n response: NextResponse,\n options: {\n cookieName?: string;\n } = {}\n): NextResponse {\n const cookieName = options.cookieName ?? 'stackwright_session';\n response.cookies.delete(cookieName);\n return response;\n}\n"],"mappings":"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","names":["import_auth","import_server"]}
package/dist/index.mjs CHANGED
@@ -117,14 +117,80 @@ function clearSessionCookie(res, options = {}) {
117
117
  // src/index.ts
118
118
  import { RBACEngine } from "@stackwright-pro/auth";
119
119
  import { AuthProvider, AuthContext, useAuth, useRequireAuth } from "@stackwright-pro/auth";
120
+
121
+ // src/route-handlers.ts
122
+ import { NextResponse as NextResponse2 } from "next/server";
123
+ function protectedRouteHandler(handler, config) {
124
+ const {
125
+ sessionManager,
126
+ roles,
127
+ permissions,
128
+ cookieName = "stackwright_session",
129
+ rbacEngine
130
+ } = config;
131
+ return async (request, context) => {
132
+ const sessionCookie = request.cookies.get(cookieName);
133
+ if (!sessionCookie) {
134
+ return NextResponse2.json({ error: "Not authenticated" }, { status: 401 });
135
+ }
136
+ const session = await sessionManager.deserialize(sessionCookie.value);
137
+ if (!session || sessionManager.isExpired(session)) {
138
+ return NextResponse2.json({ error: "Session expired" }, { status: 401 });
139
+ }
140
+ if (roles && roles.length > 0) {
141
+ if (rbacEngine) {
142
+ if (!rbacEngine.hasAnyRole(session.user, roles)) {
143
+ return NextResponse2.json({ error: "Insufficient permissions" }, { status: 403 });
144
+ }
145
+ } else {
146
+ const hasRole = roles.some((role) => session.user.roles.includes(role));
147
+ if (!hasRole) {
148
+ return NextResponse2.json({ error: "Insufficient permissions" }, { status: 403 });
149
+ }
150
+ }
151
+ }
152
+ if (permissions && permissions.length > 0) {
153
+ if (rbacEngine) {
154
+ if (!rbacEngine.hasAllPermissions(session.user, permissions)) {
155
+ return NextResponse2.json({ error: "Insufficient permissions" }, { status: 403 });
156
+ }
157
+ } else {
158
+ const hasAllPermissions = permissions.every((p) => session.user.permissions?.includes(p));
159
+ if (!hasAllPermissions) {
160
+ return NextResponse2.json({ error: "Insufficient permissions" }, { status: 403 });
161
+ }
162
+ }
163
+ }
164
+ return handler(request, context, session);
165
+ };
166
+ }
167
+ function setSessionCookieOnResponse(response, sessionJwt, options = {}) {
168
+ const cookieName = options.cookieName ?? "stackwright_session";
169
+ const maxAge = options.maxAge ?? 900;
170
+ response.cookies.set(cookieName, sessionJwt, {
171
+ httpOnly: true,
172
+ secure: true,
173
+ sameSite: "lax",
174
+ maxAge
175
+ });
176
+ return response;
177
+ }
178
+ function clearSessionCookieOnResponse(response, options = {}) {
179
+ const cookieName = options.cookieName ?? "stackwright_session";
180
+ response.cookies.delete(cookieName);
181
+ return response;
182
+ }
120
183
  export {
121
184
  AuthContext,
122
185
  AuthProvider,
123
186
  RBACEngine,
124
187
  clearSessionCookie,
188
+ clearSessionCookieOnResponse,
125
189
  createAuthMiddleware,
126
190
  protectedRoute,
191
+ protectedRouteHandler,
127
192
  setSessionCookie,
193
+ setSessionCookieOnResponse,
128
194
  useAuth,
129
195
  useRequireAuth
130
196
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.ts","../src/api-helpers.ts","../src/cookie-utils.ts","../src/index.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\nimport {\n SessionManager,\n RBACEngine,\n type AuthConfig,\n type AuthSession,\n} from '@stackwright-pro/auth';\n\nexport interface MiddlewareConfig {\n /**\n * Auth configuration\n */\n authConfig: AuthConfig;\n\n /**\n * Session manager instance\n */\n sessionManager: SessionManager;\n\n /**\n * RBAC engine instance\n */\n rbacEngine: RBACEngine;\n\n /**\n * Cookie name for session (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * URL to redirect to when authentication fails (default: '/login')\n */\n loginUrl?: string;\n\n /**\n * URL to redirect to when authorization fails (default: '/unauthorized')\n */\n unauthorizedUrl?: string;\n}\n\n/**\n * Create Next.js middleware for authentication and authorization\n *\n * @example\n * ```typescript\n * // middleware.ts\n * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthMiddleware({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthMiddleware(config: MiddlewareConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authMiddleware(request: NextRequest): Promise<NextResponse | undefined> {\n const { pathname } = request.nextUrl;\n\n // Check if route is public\n if (rbacEngine.isPublicRoute(pathname)) {\n return undefined; // Continue to next middleware\n }\n\n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n let session: AuthSession | null = null;\n\n if (sessionCookie) {\n session = await sessionManager.deserialize(sessionCookie.value);\n }\n\n // Check if session is valid\n if (!session || sessionManager.isExpired(session)) {\n // Redirect to login\n const url = request.nextUrl.clone();\n url.pathname = loginUrl;\n url.searchParams.set('redirect', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if user can access route\n if (!rbacEngine.canAccessRoute(session.user, pathname)) {\n // Redirect to unauthorized\n const url = request.nextUrl.clone();\n url.pathname = unauthorizedUrl;\n return NextResponse.redirect(url);\n }\n\n // Refresh session if needed\n if (sessionManager.shouldRefresh(session)) {\n const refreshed = await sessionManager.refreshSession(session);\n const newJwt = await sessionManager.serialize(refreshed);\n\n const response = NextResponse.next();\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n maxAge: 900, // 15 minutes\n });\n\n return response;\n }\n\n // Allow request to continue\n return undefined;\n };\n}\n","import type { NextApiRequest, NextApiResponse } from 'next';\nimport { SessionManager, RBACEngine, type AuthSession, type AuthUser } from '@stackwright-pro/auth';\n\nexport interface ProtectedRouteConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n\n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n\n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n\n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * Optional RBAC engine for wildcard-aware role/permission checking.\n * When provided, authorization decisions use RBACEngine (consistent\n * with middleware). Without it, falls back to direct array checks.\n */\n rbacEngine?: RBACEngine;\n}\n\n/**\n * Handler function with authenticated session\n */\nexport type AuthenticatedHandler<T = any> = (\n req: NextApiRequest,\n res: NextApiResponse<T>,\n session: AuthSession\n) => void | Promise<void>;\n\n/**\n * Wrap API route with authentication/authorization\n *\n * @example\n * ```typescript\n * // pages/api/equipment/[id].ts\n * export default protectedRoute(\n * async (req, res, session) => {\n * const equipment = await getEquipment(req.query.id);\n * res.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRoute<T = any>(\n handler: AuthenticatedHandler<T>,\n config: ProtectedRouteConfig\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const { sessionManager, roles, permissions, cookieName = 'stackwright_session' } = config;\n\n return async (req: NextApiRequest, res: NextApiResponse) => {\n // Extract session from cookie\n const sessionCookie = req.cookies[cookieName];\n\n if (!sessionCookie) {\n res.status(401).json({ error: 'Not authenticated' } as any);\n return;\n }\n\n // Verify session\n const session = await sessionManager.deserialize(sessionCookie);\n\n if (!session || sessionManager.isExpired(session)) {\n res.status(401).json({ error: 'Session expired' } as any);\n return;\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for consistent wildcard-aware checking\n if (!config.rbacEngine.hasAnyRole(session.user, roles)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasRole = roles.some((role) => session.user.roles.includes(role));\n if (!hasRole) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for wildcard-aware permission checking\n if (!config.rbacEngine.hasAllPermissions(session.user, permissions)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasAllPermissions = permissions.every((permission) =>\n session.user.permissions?.includes(permission)\n );\n if (!hasAllPermissions) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Call handler with session\n await handler(req, res, session);\n };\n}\n","import type { NextApiResponse } from 'next';\nimport { serializeCookie, clearCookie, type CookieOptions } from '@stackwright-pro/auth';\n\n/**\n * Set session cookie in Next.js API response\n */\nexport function setSessionCookie(\n res: NextApiResponse,\n sessionJwt: string,\n options: CookieOptions = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n const maxAge = options.maxAge || 900; // 15 minutes default\n\n const cookie = serializeCookie(cookieName, sessionJwt, {\n ...options,\n maxAge,\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n });\n\n res.setHeader('Set-Cookie', cookie);\n}\n\n/**\n * Clear session cookie in Next.js API response\n */\nexport function clearSessionCookie(\n res: NextApiResponse,\n options: Pick<CookieOptions, 'name' | 'domain' | 'path'> = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n\n const cookie = clearCookie(cookieName, options);\n\n res.setHeader('Set-Cookie', cookie);\n}\n","export { createAuthMiddleware, type MiddlewareConfig } from './middleware.js';\nexport {\n protectedRoute,\n type ProtectedRouteConfig,\n type AuthenticatedHandler,\n} from './api-helpers.js';\nexport { setSessionCookie, clearSessionCookie } from './cookie-utils.js';\n\n// Re-export commonly used types/classes from core\nexport type { AuthUser, AuthSession, AuthConfig } from '@stackwright-pro/auth';\n\n// Re-export RBAC engine\nexport { RBACEngine } from '@stackwright-pro/auth';\n\n// Re-export React context components for Next.js apps\nexport { AuthProvider, AuthContext, useAuth, useRequireAuth } from '@stackwright-pro/auth';\nexport type { AuthProviderProps, AuthContextValue } from '@stackwright-pro/auth';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/middleware.ts","../src/api-helpers.ts","../src/cookie-utils.ts","../src/index.ts","../src/route-handlers.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\nimport {\n SessionManager,\n RBACEngine,\n type AuthConfig,\n type AuthSession,\n} from '@stackwright-pro/auth';\n\nexport interface MiddlewareConfig {\n /**\n * Auth configuration\n */\n authConfig: AuthConfig;\n\n /**\n * Session manager instance\n */\n sessionManager: SessionManager;\n\n /**\n * RBAC engine instance\n */\n rbacEngine: RBACEngine;\n\n /**\n * Cookie name for session (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * URL to redirect to when authentication fails (default: '/login')\n */\n loginUrl?: string;\n\n /**\n * URL to redirect to when authorization fails (default: '/unauthorized')\n */\n unauthorizedUrl?: string;\n}\n\n/**\n * Create Next.js middleware for authentication and authorization\n *\n * @example\n * ```typescript\n * // middleware.ts\n * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthMiddleware({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthMiddleware(config: MiddlewareConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authMiddleware(request: NextRequest): Promise<NextResponse | undefined> {\n const { pathname } = request.nextUrl;\n\n // Check if route is public\n if (rbacEngine.isPublicRoute(pathname)) {\n return undefined; // Continue to next middleware\n }\n\n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n let session: AuthSession | null = null;\n\n if (sessionCookie) {\n session = await sessionManager.deserialize(sessionCookie.value);\n }\n\n // Check if session is valid\n if (!session || sessionManager.isExpired(session)) {\n // Redirect to login\n const url = request.nextUrl.clone();\n url.pathname = loginUrl;\n url.searchParams.set('redirect', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if user can access route\n if (!rbacEngine.canAccessRoute(session.user, pathname)) {\n // Redirect to unauthorized\n const url = request.nextUrl.clone();\n url.pathname = unauthorizedUrl;\n return NextResponse.redirect(url);\n }\n\n // Refresh session if needed\n if (sessionManager.shouldRefresh(session)) {\n const refreshed = await sessionManager.refreshSession(session);\n const newJwt = await sessionManager.serialize(refreshed);\n\n const response = NextResponse.next();\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n maxAge: 900, // 15 minutes\n });\n\n return response;\n }\n\n // Allow request to continue\n return undefined;\n };\n}\n","import type { NextApiRequest, NextApiResponse } from 'next';\nimport { SessionManager, RBACEngine, type AuthSession, type AuthUser } from '@stackwright-pro/auth';\n\nexport interface ProtectedRouteConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n\n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n\n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n\n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * Optional RBAC engine for wildcard-aware role/permission checking.\n * When provided, authorization decisions use RBACEngine (consistent\n * with middleware). Without it, falls back to direct array checks.\n */\n rbacEngine?: RBACEngine;\n}\n\n/**\n * Handler function with authenticated session\n */\nexport type AuthenticatedHandler<T = any> = (\n req: NextApiRequest,\n res: NextApiResponse<T>,\n session: AuthSession\n) => void | Promise<void>;\n\n/**\n * Wrap API route with authentication/authorization\n *\n * @example\n * ```typescript\n * // pages/api/equipment/[id].ts\n * export default protectedRoute(\n * async (req, res, session) => {\n * const equipment = await getEquipment(req.query.id);\n * res.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRoute<T = any>(\n handler: AuthenticatedHandler<T>,\n config: ProtectedRouteConfig\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const { sessionManager, roles, permissions, cookieName = 'stackwright_session' } = config;\n\n return async (req: NextApiRequest, res: NextApiResponse) => {\n // Extract session from cookie\n const sessionCookie = req.cookies[cookieName];\n\n if (!sessionCookie) {\n res.status(401).json({ error: 'Not authenticated' } as any);\n return;\n }\n\n // Verify session\n const session = await sessionManager.deserialize(sessionCookie);\n\n if (!session || sessionManager.isExpired(session)) {\n res.status(401).json({ error: 'Session expired' } as any);\n return;\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for consistent wildcard-aware checking\n if (!config.rbacEngine.hasAnyRole(session.user, roles)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasRole = roles.some((role) => session.user.roles.includes(role));\n if (!hasRole) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n if (config.rbacEngine) {\n // Use RBAC engine for wildcard-aware permission checking\n if (!config.rbacEngine.hasAllPermissions(session.user, permissions)) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n } else {\n // Fallback: direct check (no wildcard support)\n const hasAllPermissions = permissions.every((permission) =>\n session.user.permissions?.includes(permission)\n );\n if (!hasAllPermissions) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n }\n\n // Call handler with session\n await handler(req, res, session);\n };\n}\n","import type { NextApiResponse } from 'next';\nimport { serializeCookie, clearCookie, type CookieOptions } from '@stackwright-pro/auth';\n\n/**\n * Set session cookie in Next.js API response\n */\nexport function setSessionCookie(\n res: NextApiResponse,\n sessionJwt: string,\n options: CookieOptions = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n const maxAge = options.maxAge || 900; // 15 minutes default\n\n const cookie = serializeCookie(cookieName, sessionJwt, {\n ...options,\n maxAge,\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n });\n\n res.setHeader('Set-Cookie', cookie);\n}\n\n/**\n * Clear session cookie in Next.js API response\n */\nexport function clearSessionCookie(\n res: NextApiResponse,\n options: Pick<CookieOptions, 'name' | 'domain' | 'path'> = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n\n const cookie = clearCookie(cookieName, options);\n\n res.setHeader('Set-Cookie', cookie);\n}\n","export { createAuthMiddleware, type MiddlewareConfig } from './middleware.js';\nexport {\n protectedRoute,\n type ProtectedRouteConfig,\n type AuthenticatedHandler,\n} from './api-helpers.js';\nexport { setSessionCookie, clearSessionCookie } from './cookie-utils.js';\n\n// Re-export commonly used types/classes from core\nexport type { AuthUser, AuthSession, AuthConfig } from '@stackwright-pro/auth';\n\n// Re-export RBAC engine\nexport { RBACEngine } from '@stackwright-pro/auth';\n\n// Re-export React context components for Next.js apps\nexport { AuthProvider, AuthContext, useAuth, useRequireAuth } from '@stackwright-pro/auth';\nexport type { AuthProviderProps, AuthContextValue } from '@stackwright-pro/auth';\n\n// App Router Route Handler helpers (replaces api-helpers for App Router users)\nexport {\n protectedRouteHandler,\n setSessionCookieOnResponse,\n clearSessionCookieOnResponse,\n type RouteHandlerConfig,\n type AuthenticatedRouteHandler,\n} from './route-handlers.js';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport { SessionManager, RBACEngine, type AuthSession } from '@stackwright-pro/auth';\n\nexport interface RouteHandlerConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n\n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n\n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n\n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n\n /**\n * Optional RBAC engine for wildcard-aware role/permission checking.\n * When provided, authorization decisions use RBACEngine (consistent\n * with middleware). Without it, falls back to direct array checks.\n */\n rbacEngine?: RBACEngine;\n}\n\n/**\n * Handler function with authenticated session — App Router Route Handler signature.\n */\nexport type AuthenticatedRouteHandler = (\n request: NextRequest,\n context: { params: Record<string, string> },\n session: AuthSession\n) => Promise<Response> | Response;\n\n/**\n * Wrap an App Router Route Handler with authentication/authorization.\n *\n * This is the App Router equivalent of `protectedRoute()` from `api-helpers.ts`.\n *\n * @example\n * ```typescript\n * // app/api/equipment/[id]/route.ts\n * import { protectedRouteHandler } from '@stackwright-pro/auth-nextjs';\n *\n * export const GET = protectedRouteHandler(\n * async (request, { params }, session) => {\n * const equipment = await getEquipment(params.id);\n * return NextResponse.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRouteHandler(\n handler: AuthenticatedRouteHandler,\n config: RouteHandlerConfig\n): (request: NextRequest, context: { params: Record<string, string> }) => Promise<Response> {\n const {\n sessionManager,\n roles,\n permissions,\n cookieName = 'stackwright_session',\n rbacEngine,\n } = config;\n\n return async (\n request: NextRequest,\n context: { params: Record<string, string> }\n ): Promise<Response> => {\n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n\n if (!sessionCookie) {\n return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });\n }\n\n // Verify session\n const session = await sessionManager.deserialize(sessionCookie.value);\n\n if (!session || sessionManager.isExpired(session)) {\n return NextResponse.json({ error: 'Session expired' }, { status: 401 });\n }\n\n // Check roles\n if (roles && roles.length > 0) {\n if (rbacEngine) {\n if (!rbacEngine.hasAnyRole(session.user, roles)) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n } else {\n const hasRole = roles.some((role) => session.user.roles.includes(role));\n if (!hasRole) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n }\n }\n\n // Check permissions\n if (permissions && permissions.length > 0) {\n if (rbacEngine) {\n if (!rbacEngine.hasAllPermissions(session.user, permissions)) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n } else {\n const hasAllPermissions = permissions.every((p) => session.user.permissions?.includes(p));\n if (!hasAllPermissions) {\n return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });\n }\n }\n }\n\n return handler(request, context, session);\n };\n}\n\n/**\n * Set the session cookie on a `NextResponse` (App Router).\n *\n * This is the App Router equivalent of `setSessionCookie()` from `cookie-utils.ts`.\n *\n * @example\n * ```typescript\n * // app/api/auth/login/route.ts\n * import { setSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';\n *\n * export async function POST(request: NextRequest) {\n * const session = await createSession(request);\n * const jwt = await sessionManager.serialize(session);\n * const response = NextResponse.json({ ok: true });\n * return setSessionCookieOnResponse(response, jwt);\n * }\n * ```\n */\nexport function setSessionCookieOnResponse(\n response: NextResponse,\n sessionJwt: string,\n options: {\n cookieName?: string;\n maxAge?: number;\n } = {}\n): NextResponse {\n const cookieName = options.cookieName ?? 'stackwright_session';\n const maxAge = options.maxAge ?? 900; // 15 minutes\n\n response.cookies.set(cookieName, sessionJwt, {\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n maxAge,\n });\n\n return response;\n}\n\n/**\n * Clear the session cookie on a `NextResponse` (App Router).\n *\n * This is the App Router equivalent of `clearSessionCookie()` from `cookie-utils.ts`.\n *\n * @example\n * ```typescript\n * // app/api/auth/logout/route.ts\n * import { clearSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';\n *\n * export async function POST() {\n * const response = NextResponse.json({ ok: true });\n * return clearSessionCookieOnResponse(response);\n * }\n * ```\n */\nexport function clearSessionCookieOnResponse(\n response: NextResponse,\n options: {\n cookieName?: string;\n } = {}\n): NextResponse {\n const cookieName = options.cookieName ?? 'stackwright_session';\n response.cookies.delete(cookieName);\n return response;\n}\n"],"mappings":"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","names":["NextResponse"]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@stackwright-pro/auth-nextjs",
3
- "version": "0.2.0-alpha.4",
3
+ "version": "0.2.0-alpha.7",
4
4
  "description": "Next.js adapter for Stackwright Pro authentication",
5
- "license": "PROPRIETARY",
5
+ "license": "SEE LICENSE IN LICENSE",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
@@ -17,12 +17,12 @@
17
17
  "dist"
18
18
  ],
19
19
  "dependencies": {
20
- "@stackwright-pro/auth": "0.2.0-alpha.4"
20
+ "@stackwright-pro/auth": "0.2.0-alpha.5"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^25.4.0",
24
24
  "tsup": "^8.5.1",
25
- "typescript": "^5.8.3",
25
+ "typescript": "^6.0.3",
26
26
  "vitest": "^4.0.18"
27
27
  },
28
28
  "peerDependencies": {