@stackwright-pro/auth-nextjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @stackwright-pro/auth-nextjs
2
+
3
+ Next.js adapter for Stackwright Pro authentication.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @stackwright-pro/auth-nextjs
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Middleware (Route Protection)
14
+
15
+ ```typescript
16
+ // middleware.ts
17
+ import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';
18
+ import { sessionManager, rbacEngine, authConfig } from './lib/auth';
19
+
20
+ export default createAuthMiddleware({
21
+ authConfig,
22
+ sessionManager,
23
+ rbacEngine,
24
+ cookieName: 'my_session',
25
+ loginUrl: '/login',
26
+ unauthorizedUrl: '/403',
27
+ });
28
+
29
+ export const config = {
30
+ matcher: ['/((?!_next|api/auth|login).*)'],
31
+ };
32
+ ```
33
+
34
+ ### Protected API Routes
35
+
36
+ ```typescript
37
+ // pages/api/equipment/[id].ts
38
+ import { protectedRoute } from '@stackwright-pro/auth-nextjs';
39
+ import { sessionManager } from '@/lib/auth';
40
+
41
+ export default protectedRoute(
42
+ async (req, res, session) => {
43
+ const equipment = await getEquipment(req.query.id);
44
+ res.json(equipment);
45
+ },
46
+ {
47
+ sessionManager,
48
+ roles: ['ANALYST', 'ADMIN'],
49
+ }
50
+ );
51
+ ```
52
+
53
+ ### Cookie Utilities
54
+
55
+ ```typescript
56
+ // pages/api/auth/login.ts
57
+ import { setSessionCookie } from '@stackwright-pro/auth-nextjs';
58
+
59
+ export default async function handler(req, res) {
60
+ const session = await authenticateUser(req.body);
61
+ const jwt = await sessionManager.serialize(session);
62
+
63
+ setSessionCookie(res, jwt, {
64
+ maxAge: 900, // 15 minutes
65
+ });
66
+
67
+ res.json({ success: true });
68
+ }
69
+ ```
@@ -0,0 +1,104 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { AuthConfig, SessionManager, RBACEngine, AuthSession, CookieOptions } from '@stackwright-pro/auth';
3
+ export { AuthConfig, AuthSession, AuthUser } from '@stackwright-pro/auth';
4
+ import { NextApiRequest, NextApiResponse } from 'next';
5
+
6
+ interface MiddlewareConfig {
7
+ /**
8
+ * Auth configuration
9
+ */
10
+ authConfig: AuthConfig;
11
+ /**
12
+ * Session manager instance
13
+ */
14
+ sessionManager: SessionManager;
15
+ /**
16
+ * RBAC engine instance
17
+ */
18
+ rbacEngine: RBACEngine;
19
+ /**
20
+ * Cookie name for session (default: 'stackwright_session')
21
+ */
22
+ cookieName?: string;
23
+ /**
24
+ * URL to redirect to when authentication fails (default: '/login')
25
+ */
26
+ loginUrl?: string;
27
+ /**
28
+ * URL to redirect to when authorization fails (default: '/unauthorized')
29
+ */
30
+ unauthorizedUrl?: string;
31
+ }
32
+ /**
33
+ * Create Next.js middleware for authentication and authorization
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // middleware.ts
38
+ * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';
39
+ *
40
+ * export default createAuthMiddleware({
41
+ * authConfig,
42
+ * sessionManager,
43
+ * rbacEngine,
44
+ * });
45
+ *
46
+ * export const config = {
47
+ * matcher: ['/((?!_next|api/auth).*)'],
48
+ * };
49
+ * ```
50
+ */
51
+ declare function createAuthMiddleware(config: MiddlewareConfig): (request: NextRequest) => Promise<NextResponse | undefined>;
52
+
53
+ interface ProtectedRouteConfig {
54
+ /**
55
+ * Session manager for validating sessions
56
+ */
57
+ sessionManager: SessionManager;
58
+ /**
59
+ * Required roles (user must have ANY of these)
60
+ */
61
+ roles?: string[];
62
+ /**
63
+ * Required permissions (user must have ALL of these)
64
+ */
65
+ permissions?: string[];
66
+ /**
67
+ * Cookie name (default: 'stackwright_session')
68
+ */
69
+ cookieName?: string;
70
+ }
71
+ /**
72
+ * Handler function with authenticated session
73
+ */
74
+ type AuthenticatedHandler<T = any> = (req: NextApiRequest, res: NextApiResponse<T>, session: AuthSession) => void | Promise<void>;
75
+ /**
76
+ * Wrap API route with authentication/authorization
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // pages/api/equipment/[id].ts
81
+ * export default protectedRoute(
82
+ * async (req, res, session) => {
83
+ * const equipment = await getEquipment(req.query.id);
84
+ * res.json(equipment);
85
+ * },
86
+ * {
87
+ * sessionManager,
88
+ * roles: ['ANALYST', 'ADMIN'],
89
+ * }
90
+ * );
91
+ * ```
92
+ */
93
+ declare function protectedRoute<T = any>(handler: AuthenticatedHandler<T>, config: ProtectedRouteConfig): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
94
+
95
+ /**
96
+ * Set session cookie in Next.js API response
97
+ */
98
+ declare function setSessionCookie(res: NextApiResponse, sessionJwt: string, options?: CookieOptions): void;
99
+ /**
100
+ * Clear session cookie in Next.js API response
101
+ */
102
+ declare function clearSessionCookie(res: NextApiResponse, options?: Pick<CookieOptions, 'name' | 'domain' | 'path'>): void;
103
+
104
+ export { type AuthenticatedHandler, type MiddlewareConfig, type ProtectedRouteConfig, clearSessionCookie, createAuthMiddleware, protectedRoute, setSessionCookie };
@@ -0,0 +1,104 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { AuthConfig, SessionManager, RBACEngine, AuthSession, CookieOptions } from '@stackwright-pro/auth';
3
+ export { AuthConfig, AuthSession, AuthUser } from '@stackwright-pro/auth';
4
+ import { NextApiRequest, NextApiResponse } from 'next';
5
+
6
+ interface MiddlewareConfig {
7
+ /**
8
+ * Auth configuration
9
+ */
10
+ authConfig: AuthConfig;
11
+ /**
12
+ * Session manager instance
13
+ */
14
+ sessionManager: SessionManager;
15
+ /**
16
+ * RBAC engine instance
17
+ */
18
+ rbacEngine: RBACEngine;
19
+ /**
20
+ * Cookie name for session (default: 'stackwright_session')
21
+ */
22
+ cookieName?: string;
23
+ /**
24
+ * URL to redirect to when authentication fails (default: '/login')
25
+ */
26
+ loginUrl?: string;
27
+ /**
28
+ * URL to redirect to when authorization fails (default: '/unauthorized')
29
+ */
30
+ unauthorizedUrl?: string;
31
+ }
32
+ /**
33
+ * Create Next.js middleware for authentication and authorization
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // middleware.ts
38
+ * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';
39
+ *
40
+ * export default createAuthMiddleware({
41
+ * authConfig,
42
+ * sessionManager,
43
+ * rbacEngine,
44
+ * });
45
+ *
46
+ * export const config = {
47
+ * matcher: ['/((?!_next|api/auth).*)'],
48
+ * };
49
+ * ```
50
+ */
51
+ declare function createAuthMiddleware(config: MiddlewareConfig): (request: NextRequest) => Promise<NextResponse | undefined>;
52
+
53
+ interface ProtectedRouteConfig {
54
+ /**
55
+ * Session manager for validating sessions
56
+ */
57
+ sessionManager: SessionManager;
58
+ /**
59
+ * Required roles (user must have ANY of these)
60
+ */
61
+ roles?: string[];
62
+ /**
63
+ * Required permissions (user must have ALL of these)
64
+ */
65
+ permissions?: string[];
66
+ /**
67
+ * Cookie name (default: 'stackwright_session')
68
+ */
69
+ cookieName?: string;
70
+ }
71
+ /**
72
+ * Handler function with authenticated session
73
+ */
74
+ type AuthenticatedHandler<T = any> = (req: NextApiRequest, res: NextApiResponse<T>, session: AuthSession) => void | Promise<void>;
75
+ /**
76
+ * Wrap API route with authentication/authorization
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // pages/api/equipment/[id].ts
81
+ * export default protectedRoute(
82
+ * async (req, res, session) => {
83
+ * const equipment = await getEquipment(req.query.id);
84
+ * res.json(equipment);
85
+ * },
86
+ * {
87
+ * sessionManager,
88
+ * roles: ['ANALYST', 'ADMIN'],
89
+ * }
90
+ * );
91
+ * ```
92
+ */
93
+ declare function protectedRoute<T = any>(handler: AuthenticatedHandler<T>, config: ProtectedRouteConfig): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
94
+
95
+ /**
96
+ * Set session cookie in Next.js API response
97
+ */
98
+ declare function setSessionCookie(res: NextApiResponse, sessionJwt: string, options?: CookieOptions): void;
99
+ /**
100
+ * Clear session cookie in Next.js API response
101
+ */
102
+ declare function clearSessionCookie(res: NextApiResponse, options?: Pick<CookieOptions, 'name' | 'domain' | 'path'>): void;
103
+
104
+ export { type AuthenticatedHandler, type MiddlewareConfig, type ProtectedRouteConfig, clearSessionCookie, createAuthMiddleware, protectedRoute, setSessionCookie };
package/dist/index.js ADDED
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ clearSessionCookie: () => clearSessionCookie,
24
+ createAuthMiddleware: () => createAuthMiddleware,
25
+ protectedRoute: () => protectedRoute,
26
+ setSessionCookie: () => setSessionCookie
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/middleware.ts
31
+ var import_server = require("next/server");
32
+ function createAuthMiddleware(config) {
33
+ const {
34
+ sessionManager,
35
+ rbacEngine,
36
+ cookieName = "stackwright_session",
37
+ loginUrl = "/login",
38
+ unauthorizedUrl = "/unauthorized"
39
+ } = config;
40
+ return async function authMiddleware(request) {
41
+ const { pathname } = request.nextUrl;
42
+ if (rbacEngine.isPublicRoute(pathname)) {
43
+ return void 0;
44
+ }
45
+ const sessionCookie = request.cookies.get(cookieName);
46
+ let session = null;
47
+ if (sessionCookie) {
48
+ session = await sessionManager.deserialize(sessionCookie.value);
49
+ }
50
+ if (!session || sessionManager.isExpired(session)) {
51
+ const url = request.nextUrl.clone();
52
+ url.pathname = loginUrl;
53
+ url.searchParams.set("redirect", pathname);
54
+ return import_server.NextResponse.redirect(url);
55
+ }
56
+ if (!rbacEngine.canAccessRoute(session.user, pathname)) {
57
+ const url = request.nextUrl.clone();
58
+ url.pathname = unauthorizedUrl;
59
+ return import_server.NextResponse.redirect(url);
60
+ }
61
+ if (sessionManager.shouldRefresh(session)) {
62
+ const refreshed = await sessionManager.refreshSession(session);
63
+ const newJwt = await sessionManager.serialize(refreshed);
64
+ const response = import_server.NextResponse.next();
65
+ response.cookies.set(cookieName, newJwt, {
66
+ httpOnly: true,
67
+ secure: process.env.NODE_ENV === "production",
68
+ sameSite: "lax",
69
+ maxAge: 900
70
+ // 15 minutes
71
+ });
72
+ return response;
73
+ }
74
+ return void 0;
75
+ };
76
+ }
77
+
78
+ // src/api-helpers.ts
79
+ function protectedRoute(handler, config) {
80
+ const {
81
+ sessionManager,
82
+ roles,
83
+ permissions,
84
+ cookieName = "stackwright_session"
85
+ } = config;
86
+ return async (req, res) => {
87
+ const sessionCookie = req.cookies[cookieName];
88
+ if (!sessionCookie) {
89
+ res.status(401).json({ error: "Not authenticated" });
90
+ return;
91
+ }
92
+ const session = await sessionManager.deserialize(sessionCookie);
93
+ if (!session || sessionManager.isExpired(session)) {
94
+ res.status(401).json({ error: "Session expired" });
95
+ return;
96
+ }
97
+ if (roles && roles.length > 0) {
98
+ const hasRole = roles.some((role) => session.user.roles.includes(role));
99
+ if (!hasRole) {
100
+ res.status(403).json({ error: "Insufficient permissions" });
101
+ return;
102
+ }
103
+ }
104
+ if (permissions && permissions.length > 0) {
105
+ const hasAllPermissions = permissions.every(
106
+ (permission) => session.user.permissions?.includes(permission)
107
+ );
108
+ if (!hasAllPermissions) {
109
+ res.status(403).json({ error: "Insufficient permissions" });
110
+ return;
111
+ }
112
+ }
113
+ await handler(req, res, session);
114
+ };
115
+ }
116
+
117
+ // src/cookie-utils.ts
118
+ var import_auth = require("@stackwright-pro/auth");
119
+ function setSessionCookie(res, sessionJwt, options = {}) {
120
+ const cookieName = options.name || "stackwright_session";
121
+ const maxAge = options.maxAge || 900;
122
+ const cookie = (0, import_auth.serializeCookie)(cookieName, sessionJwt, {
123
+ ...options,
124
+ maxAge,
125
+ httpOnly: true,
126
+ secure: process.env.NODE_ENV === "production",
127
+ sameSite: "lax"
128
+ });
129
+ res.setHeader("Set-Cookie", cookie);
130
+ }
131
+ function clearSessionCookie(res, options = {}) {
132
+ const cookieName = options.name || "stackwright_session";
133
+ const cookie = (0, import_auth.clearCookie)(cookieName, options);
134
+ res.setHeader("Set-Cookie", cookie);
135
+ }
136
+ // Annotate the CommonJS export names for ESM import in node:
137
+ 0 && (module.exports = {
138
+ clearSessionCookie,
139
+ createAuthMiddleware,
140
+ protectedRoute,
141
+ setSessionCookie
142
+ });
143
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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 from core\nexport type { \n AuthUser, \n AuthSession, \n AuthConfig \n} from '@stackwright-pro/auth';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport { SessionManager, RBACEngine, type AuthConfig, type AuthSession } 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(\n request: NextRequest\n ): 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: process.env.NODE_ENV === 'production',\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, 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/**\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 {\n sessionManager,\n roles,\n permissions,\n cookieName = 'stackwright_session',\n } = 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 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 // Check permissions\n if (permissions && permissions.length > 0) {\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 // 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: process.env.NODE_ENV === 'production',\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":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,113 @@
1
+ // src/middleware.ts
2
+ import { NextResponse } from "next/server";
3
+ function createAuthMiddleware(config) {
4
+ const {
5
+ sessionManager,
6
+ rbacEngine,
7
+ cookieName = "stackwright_session",
8
+ loginUrl = "/login",
9
+ unauthorizedUrl = "/unauthorized"
10
+ } = config;
11
+ return async function authMiddleware(request) {
12
+ const { pathname } = request.nextUrl;
13
+ if (rbacEngine.isPublicRoute(pathname)) {
14
+ return void 0;
15
+ }
16
+ const sessionCookie = request.cookies.get(cookieName);
17
+ let session = null;
18
+ if (sessionCookie) {
19
+ session = await sessionManager.deserialize(sessionCookie.value);
20
+ }
21
+ if (!session || sessionManager.isExpired(session)) {
22
+ const url = request.nextUrl.clone();
23
+ url.pathname = loginUrl;
24
+ url.searchParams.set("redirect", pathname);
25
+ return NextResponse.redirect(url);
26
+ }
27
+ if (!rbacEngine.canAccessRoute(session.user, pathname)) {
28
+ const url = request.nextUrl.clone();
29
+ url.pathname = unauthorizedUrl;
30
+ return NextResponse.redirect(url);
31
+ }
32
+ if (sessionManager.shouldRefresh(session)) {
33
+ const refreshed = await sessionManager.refreshSession(session);
34
+ const newJwt = await sessionManager.serialize(refreshed);
35
+ const response = NextResponse.next();
36
+ response.cookies.set(cookieName, newJwt, {
37
+ httpOnly: true,
38
+ secure: process.env.NODE_ENV === "production",
39
+ sameSite: "lax",
40
+ maxAge: 900
41
+ // 15 minutes
42
+ });
43
+ return response;
44
+ }
45
+ return void 0;
46
+ };
47
+ }
48
+
49
+ // src/api-helpers.ts
50
+ function protectedRoute(handler, config) {
51
+ const {
52
+ sessionManager,
53
+ roles,
54
+ permissions,
55
+ cookieName = "stackwright_session"
56
+ } = config;
57
+ return async (req, res) => {
58
+ const sessionCookie = req.cookies[cookieName];
59
+ if (!sessionCookie) {
60
+ res.status(401).json({ error: "Not authenticated" });
61
+ return;
62
+ }
63
+ const session = await sessionManager.deserialize(sessionCookie);
64
+ if (!session || sessionManager.isExpired(session)) {
65
+ res.status(401).json({ error: "Session expired" });
66
+ return;
67
+ }
68
+ if (roles && roles.length > 0) {
69
+ const hasRole = roles.some((role) => session.user.roles.includes(role));
70
+ if (!hasRole) {
71
+ res.status(403).json({ error: "Insufficient permissions" });
72
+ return;
73
+ }
74
+ }
75
+ if (permissions && permissions.length > 0) {
76
+ const hasAllPermissions = permissions.every(
77
+ (permission) => session.user.permissions?.includes(permission)
78
+ );
79
+ if (!hasAllPermissions) {
80
+ res.status(403).json({ error: "Insufficient permissions" });
81
+ return;
82
+ }
83
+ }
84
+ await handler(req, res, session);
85
+ };
86
+ }
87
+
88
+ // src/cookie-utils.ts
89
+ import { serializeCookie, clearCookie } from "@stackwright-pro/auth";
90
+ function setSessionCookie(res, sessionJwt, options = {}) {
91
+ const cookieName = options.name || "stackwright_session";
92
+ const maxAge = options.maxAge || 900;
93
+ const cookie = serializeCookie(cookieName, sessionJwt, {
94
+ ...options,
95
+ maxAge,
96
+ httpOnly: true,
97
+ secure: process.env.NODE_ENV === "production",
98
+ sameSite: "lax"
99
+ });
100
+ res.setHeader("Set-Cookie", cookie);
101
+ }
102
+ function clearSessionCookie(res, options = {}) {
103
+ const cookieName = options.name || "stackwright_session";
104
+ const cookie = clearCookie(cookieName, options);
105
+ res.setHeader("Set-Cookie", cookie);
106
+ }
107
+ export {
108
+ clearSessionCookie,
109
+ createAuthMiddleware,
110
+ protectedRoute,
111
+ setSessionCookie
112
+ };
113
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts","../src/api-helpers.ts","../src/cookie-utils.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\nimport { SessionManager, RBACEngine, type AuthConfig, type AuthSession } 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(\n request: NextRequest\n ): 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: process.env.NODE_ENV === 'production',\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, 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/**\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 {\n sessionManager,\n roles,\n permissions,\n cookieName = 'stackwright_session',\n } = 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 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 // Check permissions\n if (permissions && permissions.length > 0) {\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 // 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: process.env.NODE_ENV === 'production',\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":[]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@stackwright-pro/auth-nextjs",
3
+ "version": "0.1.0",
4
+ "description": "Next.js adapter for Stackwright Pro authentication",
5
+ "license": "PROPRIETARY",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "@stackwright-pro/auth": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.4.0",
24
+ "tsup": "^8.5.1",
25
+ "typescript": "^5.8.3",
26
+ "vitest": "^4.0.18"
27
+ },
28
+ "peerDependencies": {
29
+ "next": ">=14.0.0",
30
+ "react": "^19",
31
+ "react-dom": "^19"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
38
+ }
39
+ }