@stackwright-pro/auth-nextjs 0.2.0-alpha.4 → 0.3.0-alpha.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/LICENSE +21 -0
- package/README.md +36 -4
- package/dist/chunk-MFZSH4YL.mjs +58 -0
- package/dist/chunk-MFZSH4YL.mjs.map +1 -0
- package/dist/index.d.mts +119 -33
- package/dist/index.d.ts +119 -33
- package/dist/index.js +86 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +74 -44
- package/dist/index.mjs.map +1 -1
- package/dist/proxy-Blj2kIJp.d.mts +67 -0
- package/dist/proxy-Blj2kIJp.d.ts +67 -0
- package/dist/proxy.d.mts +4 -0
- package/dist/proxy.d.ts +4 -0
- package/dist/proxy.js +83 -0
- package/dist/proxy.js.map +1 -0
- package/dist/proxy.mjs +7 -0
- package/dist/proxy.mjs.map +1 -0
- package/package.json +12 -6
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/README.md
CHANGED
|
@@ -10,10 +10,35 @@ pnpm add @stackwright-pro/auth-nextjs
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Proxy (Next.js >=16 — Recommended)
|
|
14
|
+
|
|
15
|
+
Next.js 16 deprecated the `middleware.ts` convention in favor of `proxy.ts`. Use `createAuthProxy` for new projects:
|
|
14
16
|
|
|
15
17
|
```typescript
|
|
16
|
-
//
|
|
18
|
+
// proxy.ts (project root)
|
|
19
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
20
|
+
import { sessionManager, rbacEngine, authConfig } from './lib/auth';
|
|
21
|
+
|
|
22
|
+
export default createAuthProxy({
|
|
23
|
+
authConfig,
|
|
24
|
+
sessionManager,
|
|
25
|
+
rbacEngine,
|
|
26
|
+
cookieName: 'my_session',
|
|
27
|
+
loginUrl: '/login',
|
|
28
|
+
unauthorizedUrl: '/403',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const config = {
|
|
32
|
+
matcher: ['/((?!_next|api/auth|login).*)'],
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Middleware (Next.js 14–15 — Legacy)
|
|
37
|
+
|
|
38
|
+
> **Deprecated**: For Next.js >=16, use `createAuthProxy` above instead.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// middleware.ts (project root)
|
|
17
42
|
import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';
|
|
18
43
|
import { sessionManager, rbacEngine, authConfig } from './lib/auth';
|
|
19
44
|
|
|
@@ -59,11 +84,18 @@ import { setSessionCookie } from '@stackwright-pro/auth-nextjs';
|
|
|
59
84
|
export default async function handler(req, res) {
|
|
60
85
|
const session = await authenticateUser(req.body);
|
|
61
86
|
const jwt = await sessionManager.serialize(session);
|
|
62
|
-
|
|
87
|
+
|
|
63
88
|
setSessionCookie(res, jwt, {
|
|
64
89
|
maxAge: 900, // 15 minutes
|
|
65
90
|
});
|
|
66
|
-
|
|
91
|
+
|
|
67
92
|
res.json({ success: true });
|
|
68
93
|
}
|
|
69
94
|
```
|
|
95
|
+
|
|
96
|
+
## Subpath Exports
|
|
97
|
+
|
|
98
|
+
| Import path | Convention | Next.js version |
|
|
99
|
+
| ------------------------------------ | --------------------------- | --------------- |
|
|
100
|
+
| `@stackwright-pro/auth-nextjs` | Both exports available | All |
|
|
101
|
+
| `@stackwright-pro/auth-nextjs/proxy` | Proxy only (tree-shakeable) | >=16 |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/auth-handler.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
function createAuthHandler(config) {
|
|
4
|
+
const {
|
|
5
|
+
sessionManager,
|
|
6
|
+
rbacEngine,
|
|
7
|
+
cookieName = "stackwright_session",
|
|
8
|
+
loginUrl = "/login",
|
|
9
|
+
unauthorizedUrl = "/unauthorized"
|
|
10
|
+
} = config;
|
|
11
|
+
return async function authHandler(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: true,
|
|
39
|
+
sameSite: "lax",
|
|
40
|
+
maxAge: 900
|
|
41
|
+
// 15 minutes
|
|
42
|
+
});
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
return void 0;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/proxy.ts
|
|
50
|
+
function createAuthProxy(config) {
|
|
51
|
+
return createAuthHandler(config);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
createAuthHandler,
|
|
56
|
+
createAuthProxy
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-MFZSH4YL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth-handler.ts","../src/proxy.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 AuthHandlerConfig {\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 * Core auth handler shared by both the middleware (Next.js <16) and\n * proxy (Next.js >=16) conventions.\n *\n * @internal — consumers should use `createAuthMiddleware` or `createAuthProxy`.\n */\nexport function createAuthHandler(config: AuthHandlerConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authHandler(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 handler\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 { createAuthHandler, type AuthHandlerConfig } from './auth-handler.js';\n\n/**\n * Configuration for Next.js proxy-based auth (Next.js >=16).\n */\nexport type ProxyConfig = AuthHandlerConfig;\n\n/**\n * Create a Next.js proxy handler for authentication and authorization.\n *\n * For Next.js >=16 projects that use the `proxy.ts` file convention,\n * replacing the deprecated `middleware.ts` convention.\n *\n * @example\n * ```typescript\n * // proxy.ts (Next.js >=16)\n * import { createAuthProxy } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthProxy({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthProxy(config: ProxyConfig) {\n return createAuthHandler(config);\n}\n"],"mappings":";AAAA,SAA2B,oBAAoB;AA8CxC,SAAS,kBAAkB,QAA2B;AAC3D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB,IAAI;AAEJ,SAAO,eAAe,YAAY,SAAyD;AACzF,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAI,WAAW,cAAc,QAAQ,GAAG;AACtC,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,QAAQ,QAAQ,IAAI,UAAU;AACpD,QAAI,UAA8B;AAElC,QAAI,eAAe;AACjB,gBAAU,MAAM,eAAe,YAAY,cAAc,KAAK;AAAA,IAChE;AAGA,QAAI,CAAC,WAAW,eAAe,UAAU,OAAO,GAAG;AAEjD,YAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,UAAI,WAAW;AACf,UAAI,aAAa,IAAI,YAAY,QAAQ;AACzC,aAAO,aAAa,SAAS,GAAG;AAAA,IAClC;AAGA,QAAI,CAAC,WAAW,eAAe,QAAQ,MAAM,QAAQ,GAAG;AAEtD,YAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,UAAI,WAAW;AACf,aAAO,aAAa,SAAS,GAAG;AAAA,IAClC;AAGA,QAAI,eAAe,cAAc,OAAO,GAAG;AACzC,YAAM,YAAY,MAAM,eAAe,eAAe,OAAO;AAC7D,YAAM,SAAS,MAAM,eAAe,UAAU,SAAS;AAEvD,YAAM,WAAW,aAAa,KAAK;AACnC,eAAS,QAAQ,IAAI,YAAY,QAAQ;AAAA,QACvC,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,QAAQ;AAAA;AAAA,MACV,CAAC;AAED,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AACF;;;AC9EO,SAAS,gBAAgB,QAAqB;AACnD,SAAO,kBAAkB,MAAM;AACjC;","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,40 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
1
|
+
import * as next_server_js from 'next/server.js';
|
|
2
|
+
import { A as AuthHandlerConfig } from './proxy-Blj2kIJp.mjs';
|
|
3
|
+
export { P as ProxyConfig, c as createAuthHandler, a as createAuthProxy } from './proxy-Blj2kIJp.mjs';
|
|
4
4
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
|
+
import { AuthSession, SessionManager, RBACEngine, CookieOptions } from '@stackwright-pro/auth';
|
|
6
|
+
export { AuthConfig, AuthContext, AuthContextValue, AuthProvider, AuthProviderProps, AuthSession, AuthUser, RBACEngine, useAuth, useRequireAuth } from '@stackwright-pro/auth';
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
5
8
|
|
|
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
9
|
/**
|
|
33
|
-
*
|
|
10
|
+
* Configuration for Next.js middleware-based auth (Next.js <16).
|
|
11
|
+
*
|
|
12
|
+
* @deprecated For Next.js >=16, use {@link ProxyConfig} with {@link createAuthProxy} instead.
|
|
13
|
+
* The `middleware.ts` file convention is deprecated in Next.js 16.
|
|
14
|
+
*/
|
|
15
|
+
type MiddlewareConfig = AuthHandlerConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Create Next.js middleware for authentication and authorization.
|
|
18
|
+
*
|
|
19
|
+
* For Next.js 14–15 projects that use the `middleware.ts` file convention.
|
|
20
|
+
*
|
|
21
|
+
* @deprecated For Next.js >=16, use {@link createAuthProxy} instead.
|
|
22
|
+
* The `middleware.ts` file convention is deprecated in Next.js 16 in favor of `proxy.ts`.
|
|
34
23
|
*
|
|
35
24
|
* @example
|
|
36
25
|
* ```typescript
|
|
37
|
-
* // middleware.ts
|
|
26
|
+
* // middleware.ts (Next.js 14–15)
|
|
38
27
|
* import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';
|
|
39
28
|
*
|
|
40
29
|
* export default createAuthMiddleware({
|
|
@@ -48,7 +37,7 @@ interface MiddlewareConfig {
|
|
|
48
37
|
* };
|
|
49
38
|
* ```
|
|
50
39
|
*/
|
|
51
|
-
declare function createAuthMiddleware(config: MiddlewareConfig): (request: NextRequest) => Promise<NextResponse | undefined>;
|
|
40
|
+
declare function createAuthMiddleware(config: MiddlewareConfig): (request: next_server_js.NextRequest) => Promise<next_server_js.NextResponse | undefined>;
|
|
52
41
|
|
|
53
42
|
interface ProtectedRouteConfig {
|
|
54
43
|
/**
|
|
@@ -107,4 +96,101 @@ declare function setSessionCookie(res: NextApiResponse, sessionJwt: string, opti
|
|
|
107
96
|
*/
|
|
108
97
|
declare function clearSessionCookie(res: NextApiResponse, options?: Pick<CookieOptions, 'name' | 'domain' | 'path'>): void;
|
|
109
98
|
|
|
110
|
-
|
|
99
|
+
interface RouteHandlerConfig {
|
|
100
|
+
/**
|
|
101
|
+
* Session manager for validating sessions
|
|
102
|
+
*/
|
|
103
|
+
sessionManager: SessionManager;
|
|
104
|
+
/**
|
|
105
|
+
* Required roles (user must have ANY of these)
|
|
106
|
+
*/
|
|
107
|
+
roles?: string[];
|
|
108
|
+
/**
|
|
109
|
+
* Required permissions (user must have ALL of these)
|
|
110
|
+
*/
|
|
111
|
+
permissions?: string[];
|
|
112
|
+
/**
|
|
113
|
+
* Cookie name (default: 'stackwright_session')
|
|
114
|
+
*/
|
|
115
|
+
cookieName?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Optional RBAC engine for wildcard-aware role/permission checking.
|
|
118
|
+
* When provided, authorization decisions use RBACEngine (consistent
|
|
119
|
+
* with middleware). Without it, falls back to direct array checks.
|
|
120
|
+
*/
|
|
121
|
+
rbacEngine?: RBACEngine;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Handler function with authenticated session — App Router Route Handler signature.
|
|
125
|
+
*/
|
|
126
|
+
type AuthenticatedRouteHandler = (request: NextRequest, context: {
|
|
127
|
+
params: Record<string, string>;
|
|
128
|
+
}, session: AuthSession) => Promise<Response> | Response;
|
|
129
|
+
/**
|
|
130
|
+
* Wrap an App Router Route Handler with authentication/authorization.
|
|
131
|
+
*
|
|
132
|
+
* This is the App Router equivalent of `protectedRoute()` from `api-helpers.ts`.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* // app/api/equipment/[id]/route.ts
|
|
137
|
+
* import { protectedRouteHandler } from '@stackwright-pro/auth-nextjs';
|
|
138
|
+
*
|
|
139
|
+
* export const GET = protectedRouteHandler(
|
|
140
|
+
* async (request, { params }, session) => {
|
|
141
|
+
* const equipment = await getEquipment(params.id);
|
|
142
|
+
* return NextResponse.json(equipment);
|
|
143
|
+
* },
|
|
144
|
+
* {
|
|
145
|
+
* sessionManager,
|
|
146
|
+
* roles: ['ANALYST', 'ADMIN'],
|
|
147
|
+
* }
|
|
148
|
+
* );
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function protectedRouteHandler(handler: AuthenticatedRouteHandler, config: RouteHandlerConfig): (request: NextRequest, context: {
|
|
152
|
+
params: Record<string, string>;
|
|
153
|
+
}) => Promise<Response>;
|
|
154
|
+
/**
|
|
155
|
+
* Set the session cookie on a `NextResponse` (App Router).
|
|
156
|
+
*
|
|
157
|
+
* This is the App Router equivalent of `setSessionCookie()` from `cookie-utils.ts`.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* // app/api/auth/login/route.ts
|
|
162
|
+
* import { setSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
|
|
163
|
+
*
|
|
164
|
+
* export async function POST(request: NextRequest) {
|
|
165
|
+
* const session = await createSession(request);
|
|
166
|
+
* const jwt = await sessionManager.serialize(session);
|
|
167
|
+
* const response = NextResponse.json({ ok: true });
|
|
168
|
+
* return setSessionCookieOnResponse(response, jwt);
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function setSessionCookieOnResponse(response: NextResponse, sessionJwt: string, options?: {
|
|
173
|
+
cookieName?: string;
|
|
174
|
+
maxAge?: number;
|
|
175
|
+
}): NextResponse;
|
|
176
|
+
/**
|
|
177
|
+
* Clear the session cookie on a `NextResponse` (App Router).
|
|
178
|
+
*
|
|
179
|
+
* This is the App Router equivalent of `clearSessionCookie()` from `cookie-utils.ts`.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* // app/api/auth/logout/route.ts
|
|
184
|
+
* import { clearSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
|
|
185
|
+
*
|
|
186
|
+
* export async function POST() {
|
|
187
|
+
* const response = NextResponse.json({ ok: true });
|
|
188
|
+
* return clearSessionCookieOnResponse(response);
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare function clearSessionCookieOnResponse(response: NextResponse, options?: {
|
|
193
|
+
cookieName?: string;
|
|
194
|
+
}): NextResponse;
|
|
195
|
+
|
|
196
|
+
export { AuthHandlerConfig, type AuthenticatedHandler, type AuthenticatedRouteHandler, type MiddlewareConfig, type ProtectedRouteConfig, type RouteHandlerConfig, clearSessionCookie, clearSessionCookieOnResponse, createAuthMiddleware, protectedRoute, protectedRouteHandler, setSessionCookie, setSessionCookieOnResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,40 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
1
|
+
import * as next_server_js from 'next/server.js';
|
|
2
|
+
import { A as AuthHandlerConfig } from './proxy-Blj2kIJp.js';
|
|
3
|
+
export { P as ProxyConfig, c as createAuthHandler, a as createAuthProxy } from './proxy-Blj2kIJp.js';
|
|
4
4
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
|
+
import { AuthSession, SessionManager, RBACEngine, CookieOptions } from '@stackwright-pro/auth';
|
|
6
|
+
export { AuthConfig, AuthContext, AuthContextValue, AuthProvider, AuthProviderProps, AuthSession, AuthUser, RBACEngine, useAuth, useRequireAuth } from '@stackwright-pro/auth';
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
5
8
|
|
|
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
9
|
/**
|
|
33
|
-
*
|
|
10
|
+
* Configuration for Next.js middleware-based auth (Next.js <16).
|
|
11
|
+
*
|
|
12
|
+
* @deprecated For Next.js >=16, use {@link ProxyConfig} with {@link createAuthProxy} instead.
|
|
13
|
+
* The `middleware.ts` file convention is deprecated in Next.js 16.
|
|
14
|
+
*/
|
|
15
|
+
type MiddlewareConfig = AuthHandlerConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Create Next.js middleware for authentication and authorization.
|
|
18
|
+
*
|
|
19
|
+
* For Next.js 14–15 projects that use the `middleware.ts` file convention.
|
|
20
|
+
*
|
|
21
|
+
* @deprecated For Next.js >=16, use {@link createAuthProxy} instead.
|
|
22
|
+
* The `middleware.ts` file convention is deprecated in Next.js 16 in favor of `proxy.ts`.
|
|
34
23
|
*
|
|
35
24
|
* @example
|
|
36
25
|
* ```typescript
|
|
37
|
-
* // middleware.ts
|
|
26
|
+
* // middleware.ts (Next.js 14–15)
|
|
38
27
|
* import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';
|
|
39
28
|
*
|
|
40
29
|
* export default createAuthMiddleware({
|
|
@@ -48,7 +37,7 @@ interface MiddlewareConfig {
|
|
|
48
37
|
* };
|
|
49
38
|
* ```
|
|
50
39
|
*/
|
|
51
|
-
declare function createAuthMiddleware(config: MiddlewareConfig): (request: NextRequest) => Promise<NextResponse | undefined>;
|
|
40
|
+
declare function createAuthMiddleware(config: MiddlewareConfig): (request: next_server_js.NextRequest) => Promise<next_server_js.NextResponse | undefined>;
|
|
52
41
|
|
|
53
42
|
interface ProtectedRouteConfig {
|
|
54
43
|
/**
|
|
@@ -107,4 +96,101 @@ declare function setSessionCookie(res: NextApiResponse, sessionJwt: string, opti
|
|
|
107
96
|
*/
|
|
108
97
|
declare function clearSessionCookie(res: NextApiResponse, options?: Pick<CookieOptions, 'name' | 'domain' | 'path'>): void;
|
|
109
98
|
|
|
110
|
-
|
|
99
|
+
interface RouteHandlerConfig {
|
|
100
|
+
/**
|
|
101
|
+
* Session manager for validating sessions
|
|
102
|
+
*/
|
|
103
|
+
sessionManager: SessionManager;
|
|
104
|
+
/**
|
|
105
|
+
* Required roles (user must have ANY of these)
|
|
106
|
+
*/
|
|
107
|
+
roles?: string[];
|
|
108
|
+
/**
|
|
109
|
+
* Required permissions (user must have ALL of these)
|
|
110
|
+
*/
|
|
111
|
+
permissions?: string[];
|
|
112
|
+
/**
|
|
113
|
+
* Cookie name (default: 'stackwright_session')
|
|
114
|
+
*/
|
|
115
|
+
cookieName?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Optional RBAC engine for wildcard-aware role/permission checking.
|
|
118
|
+
* When provided, authorization decisions use RBACEngine (consistent
|
|
119
|
+
* with middleware). Without it, falls back to direct array checks.
|
|
120
|
+
*/
|
|
121
|
+
rbacEngine?: RBACEngine;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Handler function with authenticated session — App Router Route Handler signature.
|
|
125
|
+
*/
|
|
126
|
+
type AuthenticatedRouteHandler = (request: NextRequest, context: {
|
|
127
|
+
params: Record<string, string>;
|
|
128
|
+
}, session: AuthSession) => Promise<Response> | Response;
|
|
129
|
+
/**
|
|
130
|
+
* Wrap an App Router Route Handler with authentication/authorization.
|
|
131
|
+
*
|
|
132
|
+
* This is the App Router equivalent of `protectedRoute()` from `api-helpers.ts`.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* // app/api/equipment/[id]/route.ts
|
|
137
|
+
* import { protectedRouteHandler } from '@stackwright-pro/auth-nextjs';
|
|
138
|
+
*
|
|
139
|
+
* export const GET = protectedRouteHandler(
|
|
140
|
+
* async (request, { params }, session) => {
|
|
141
|
+
* const equipment = await getEquipment(params.id);
|
|
142
|
+
* return NextResponse.json(equipment);
|
|
143
|
+
* },
|
|
144
|
+
* {
|
|
145
|
+
* sessionManager,
|
|
146
|
+
* roles: ['ANALYST', 'ADMIN'],
|
|
147
|
+
* }
|
|
148
|
+
* );
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function protectedRouteHandler(handler: AuthenticatedRouteHandler, config: RouteHandlerConfig): (request: NextRequest, context: {
|
|
152
|
+
params: Record<string, string>;
|
|
153
|
+
}) => Promise<Response>;
|
|
154
|
+
/**
|
|
155
|
+
* Set the session cookie on a `NextResponse` (App Router).
|
|
156
|
+
*
|
|
157
|
+
* This is the App Router equivalent of `setSessionCookie()` from `cookie-utils.ts`.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* // app/api/auth/login/route.ts
|
|
162
|
+
* import { setSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
|
|
163
|
+
*
|
|
164
|
+
* export async function POST(request: NextRequest) {
|
|
165
|
+
* const session = await createSession(request);
|
|
166
|
+
* const jwt = await sessionManager.serialize(session);
|
|
167
|
+
* const response = NextResponse.json({ ok: true });
|
|
168
|
+
* return setSessionCookieOnResponse(response, jwt);
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function setSessionCookieOnResponse(response: NextResponse, sessionJwt: string, options?: {
|
|
173
|
+
cookieName?: string;
|
|
174
|
+
maxAge?: number;
|
|
175
|
+
}): NextResponse;
|
|
176
|
+
/**
|
|
177
|
+
* Clear the session cookie on a `NextResponse` (App Router).
|
|
178
|
+
*
|
|
179
|
+
* This is the App Router equivalent of `clearSessionCookie()` from `cookie-utils.ts`.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* // app/api/auth/logout/route.ts
|
|
184
|
+
* import { clearSessionCookieOnResponse } from '@stackwright-pro/auth-nextjs';
|
|
185
|
+
*
|
|
186
|
+
* export async function POST() {
|
|
187
|
+
* const response = NextResponse.json({ ok: true });
|
|
188
|
+
* return clearSessionCookieOnResponse(response);
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare function clearSessionCookieOnResponse(response: NextResponse, options?: {
|
|
193
|
+
cookieName?: string;
|
|
194
|
+
}): NextResponse;
|
|
195
|
+
|
|
196
|
+
export { AuthHandlerConfig, type AuthenticatedHandler, type AuthenticatedRouteHandler, type MiddlewareConfig, type ProtectedRouteConfig, type RouteHandlerConfig, clearSessionCookie, clearSessionCookieOnResponse, createAuthMiddleware, protectedRoute, protectedRouteHandler, setSessionCookie, setSessionCookieOnResponse };
|
package/dist/index.js
CHANGED
|
@@ -24,17 +24,22 @@ __export(index_exports, {
|
|
|
24
24
|
AuthProvider: () => import_auth3.AuthProvider,
|
|
25
25
|
RBACEngine: () => import_auth2.RBACEngine,
|
|
26
26
|
clearSessionCookie: () => clearSessionCookie,
|
|
27
|
+
clearSessionCookieOnResponse: () => clearSessionCookieOnResponse,
|
|
28
|
+
createAuthHandler: () => createAuthHandler,
|
|
27
29
|
createAuthMiddleware: () => createAuthMiddleware,
|
|
30
|
+
createAuthProxy: () => createAuthProxy,
|
|
28
31
|
protectedRoute: () => protectedRoute,
|
|
32
|
+
protectedRouteHandler: () => protectedRouteHandler,
|
|
29
33
|
setSessionCookie: () => setSessionCookie,
|
|
34
|
+
setSessionCookieOnResponse: () => setSessionCookieOnResponse,
|
|
30
35
|
useAuth: () => import_auth3.useAuth,
|
|
31
36
|
useRequireAuth: () => import_auth3.useRequireAuth
|
|
32
37
|
});
|
|
33
38
|
module.exports = __toCommonJS(index_exports);
|
|
34
39
|
|
|
35
|
-
// src/
|
|
40
|
+
// src/auth-handler.ts
|
|
36
41
|
var import_server = require("next/server");
|
|
37
|
-
function
|
|
42
|
+
function createAuthHandler(config) {
|
|
38
43
|
const {
|
|
39
44
|
sessionManager,
|
|
40
45
|
rbacEngine,
|
|
@@ -42,7 +47,7 @@ function createAuthMiddleware(config) {
|
|
|
42
47
|
loginUrl = "/login",
|
|
43
48
|
unauthorizedUrl = "/unauthorized"
|
|
44
49
|
} = config;
|
|
45
|
-
return async function
|
|
50
|
+
return async function authHandler(request) {
|
|
46
51
|
const { pathname } = request.nextUrl;
|
|
47
52
|
if (rbacEngine.isPublicRoute(pathname)) {
|
|
48
53
|
return void 0;
|
|
@@ -80,6 +85,16 @@ function createAuthMiddleware(config) {
|
|
|
80
85
|
};
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
// src/middleware.ts
|
|
89
|
+
function createAuthMiddleware(config) {
|
|
90
|
+
return createAuthHandler(config);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/proxy.ts
|
|
94
|
+
function createAuthProxy(config) {
|
|
95
|
+
return createAuthHandler(config);
|
|
96
|
+
}
|
|
97
|
+
|
|
83
98
|
// src/api-helpers.ts
|
|
84
99
|
function protectedRoute(handler, config) {
|
|
85
100
|
const { sessionManager, roles, permissions, cookieName = "stackwright_session" } = config;
|
|
@@ -151,15 +166,83 @@ function clearSessionCookie(res, options = {}) {
|
|
|
151
166
|
// src/index.ts
|
|
152
167
|
var import_auth2 = require("@stackwright-pro/auth");
|
|
153
168
|
var import_auth3 = require("@stackwright-pro/auth");
|
|
169
|
+
|
|
170
|
+
// src/route-handlers.ts
|
|
171
|
+
var import_server2 = require("next/server");
|
|
172
|
+
function protectedRouteHandler(handler, config) {
|
|
173
|
+
const {
|
|
174
|
+
sessionManager,
|
|
175
|
+
roles,
|
|
176
|
+
permissions,
|
|
177
|
+
cookieName = "stackwright_session",
|
|
178
|
+
rbacEngine
|
|
179
|
+
} = config;
|
|
180
|
+
return async (request, context) => {
|
|
181
|
+
const sessionCookie = request.cookies.get(cookieName);
|
|
182
|
+
if (!sessionCookie) {
|
|
183
|
+
return import_server2.NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
|
184
|
+
}
|
|
185
|
+
const session = await sessionManager.deserialize(sessionCookie.value);
|
|
186
|
+
if (!session || sessionManager.isExpired(session)) {
|
|
187
|
+
return import_server2.NextResponse.json({ error: "Session expired" }, { status: 401 });
|
|
188
|
+
}
|
|
189
|
+
if (roles && roles.length > 0) {
|
|
190
|
+
if (rbacEngine) {
|
|
191
|
+
if (!rbacEngine.hasAnyRole(session.user, roles)) {
|
|
192
|
+
return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const hasRole = roles.some((role) => session.user.roles.includes(role));
|
|
196
|
+
if (!hasRole) {
|
|
197
|
+
return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (permissions && permissions.length > 0) {
|
|
202
|
+
if (rbacEngine) {
|
|
203
|
+
if (!rbacEngine.hasAllPermissions(session.user, permissions)) {
|
|
204
|
+
return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
const hasAllPermissions = permissions.every((p) => session.user.permissions?.includes(p));
|
|
208
|
+
if (!hasAllPermissions) {
|
|
209
|
+
return import_server2.NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return handler(request, context, session);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function setSessionCookieOnResponse(response, sessionJwt, options = {}) {
|
|
217
|
+
const cookieName = options.cookieName ?? "stackwright_session";
|
|
218
|
+
const maxAge = options.maxAge ?? 900;
|
|
219
|
+
response.cookies.set(cookieName, sessionJwt, {
|
|
220
|
+
httpOnly: true,
|
|
221
|
+
secure: true,
|
|
222
|
+
sameSite: "lax",
|
|
223
|
+
maxAge
|
|
224
|
+
});
|
|
225
|
+
return response;
|
|
226
|
+
}
|
|
227
|
+
function clearSessionCookieOnResponse(response, options = {}) {
|
|
228
|
+
const cookieName = options.cookieName ?? "stackwright_session";
|
|
229
|
+
response.cookies.delete(cookieName);
|
|
230
|
+
return response;
|
|
231
|
+
}
|
|
154
232
|
// Annotate the CommonJS export names for ESM import in node:
|
|
155
233
|
0 && (module.exports = {
|
|
156
234
|
AuthContext,
|
|
157
235
|
AuthProvider,
|
|
158
236
|
RBACEngine,
|
|
159
237
|
clearSessionCookie,
|
|
238
|
+
clearSessionCookieOnResponse,
|
|
239
|
+
createAuthHandler,
|
|
160
240
|
createAuthMiddleware,
|
|
241
|
+
createAuthProxy,
|
|
161
242
|
protectedRoute,
|
|
243
|
+
protectedRouteHandler,
|
|
162
244
|
setSessionCookie,
|
|
245
|
+
setSessionCookieOnResponse,
|
|
163
246
|
useAuth,
|
|
164
247
|
useRequireAuth
|
|
165
248
|
});
|
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/auth-handler.ts","../src/middleware.ts","../src/proxy.ts","../src/api-helpers.ts","../src/cookie-utils.ts","../src/route-handlers.ts"],"sourcesContent":["export { createAuthMiddleware, type MiddlewareConfig } from './middleware.js';\nexport { createAuthProxy, type ProxyConfig } from './proxy.js';\nexport { createAuthHandler, type AuthHandlerConfig } from './auth-handler.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 AuthHandlerConfig {\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 * Core auth handler shared by both the middleware (Next.js <16) and\n * proxy (Next.js >=16) conventions.\n *\n * @internal — consumers should use `createAuthMiddleware` or `createAuthProxy`.\n */\nexport function createAuthHandler(config: AuthHandlerConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authHandler(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 handler\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 { createAuthHandler, type AuthHandlerConfig } from './auth-handler.js';\n\n/**\n * Configuration for Next.js middleware-based auth (Next.js <16).\n *\n * @deprecated For Next.js >=16, use {@link ProxyConfig} with {@link createAuthProxy} instead.\n * The `middleware.ts` file convention is deprecated in Next.js 16.\n */\nexport type MiddlewareConfig = AuthHandlerConfig;\n\n/**\n * Create Next.js middleware for authentication and authorization.\n *\n * For Next.js 14–15 projects that use the `middleware.ts` file convention.\n *\n * @deprecated For Next.js >=16, use {@link createAuthProxy} instead.\n * The `middleware.ts` file convention is deprecated in Next.js 16 in favor of `proxy.ts`.\n *\n * @example\n * ```typescript\n * // middleware.ts (Next.js 14–15)\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 return createAuthHandler(config);\n}\n","import { createAuthHandler, type AuthHandlerConfig } from './auth-handler.js';\n\n/**\n * Configuration for Next.js proxy-based auth (Next.js >=16).\n */\nexport type ProxyConfig = AuthHandlerConfig;\n\n/**\n * Create a Next.js proxy handler for authentication and authorization.\n *\n * For Next.js >=16 projects that use the `proxy.ts` file convention,\n * replacing the deprecated `middleware.ts` convention.\n *\n * @example\n * ```typescript\n * // proxy.ts (Next.js >=16)\n * import { createAuthProxy } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthProxy({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthProxy(config: ProxyConfig) {\n return createAuthHandler(config);\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
|
@@ -1,49 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthHandler,
|
|
3
|
+
createAuthProxy
|
|
4
|
+
} from "./chunk-MFZSH4YL.mjs";
|
|
5
|
+
|
|
1
6
|
// src/middleware.ts
|
|
2
|
-
import { NextResponse } from "next/server";
|
|
3
7
|
function createAuthMiddleware(config) {
|
|
4
|
-
|
|
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: true,
|
|
39
|
-
sameSite: "lax",
|
|
40
|
-
maxAge: 900
|
|
41
|
-
// 15 minutes
|
|
42
|
-
});
|
|
43
|
-
return response;
|
|
44
|
-
}
|
|
45
|
-
return void 0;
|
|
46
|
-
};
|
|
8
|
+
return createAuthHandler(config);
|
|
47
9
|
}
|
|
48
10
|
|
|
49
11
|
// src/api-helpers.ts
|
|
@@ -117,14 +79,82 @@ function clearSessionCookie(res, options = {}) {
|
|
|
117
79
|
// src/index.ts
|
|
118
80
|
import { RBACEngine } from "@stackwright-pro/auth";
|
|
119
81
|
import { AuthProvider, AuthContext, useAuth, useRequireAuth } from "@stackwright-pro/auth";
|
|
82
|
+
|
|
83
|
+
// src/route-handlers.ts
|
|
84
|
+
import { NextResponse } from "next/server";
|
|
85
|
+
function protectedRouteHandler(handler, config) {
|
|
86
|
+
const {
|
|
87
|
+
sessionManager,
|
|
88
|
+
roles,
|
|
89
|
+
permissions,
|
|
90
|
+
cookieName = "stackwright_session",
|
|
91
|
+
rbacEngine
|
|
92
|
+
} = config;
|
|
93
|
+
return async (request, context) => {
|
|
94
|
+
const sessionCookie = request.cookies.get(cookieName);
|
|
95
|
+
if (!sessionCookie) {
|
|
96
|
+
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
|
97
|
+
}
|
|
98
|
+
const session = await sessionManager.deserialize(sessionCookie.value);
|
|
99
|
+
if (!session || sessionManager.isExpired(session)) {
|
|
100
|
+
return NextResponse.json({ error: "Session expired" }, { status: 401 });
|
|
101
|
+
}
|
|
102
|
+
if (roles && roles.length > 0) {
|
|
103
|
+
if (rbacEngine) {
|
|
104
|
+
if (!rbacEngine.hasAnyRole(session.user, roles)) {
|
|
105
|
+
return NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
const hasRole = roles.some((role) => session.user.roles.includes(role));
|
|
109
|
+
if (!hasRole) {
|
|
110
|
+
return NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (permissions && permissions.length > 0) {
|
|
115
|
+
if (rbacEngine) {
|
|
116
|
+
if (!rbacEngine.hasAllPermissions(session.user, permissions)) {
|
|
117
|
+
return NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const hasAllPermissions = permissions.every((p) => session.user.permissions?.includes(p));
|
|
121
|
+
if (!hasAllPermissions) {
|
|
122
|
+
return NextResponse.json({ error: "Insufficient permissions" }, { status: 403 });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return handler(request, context, session);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function setSessionCookieOnResponse(response, sessionJwt, options = {}) {
|
|
130
|
+
const cookieName = options.cookieName ?? "stackwright_session";
|
|
131
|
+
const maxAge = options.maxAge ?? 900;
|
|
132
|
+
response.cookies.set(cookieName, sessionJwt, {
|
|
133
|
+
httpOnly: true,
|
|
134
|
+
secure: true,
|
|
135
|
+
sameSite: "lax",
|
|
136
|
+
maxAge
|
|
137
|
+
});
|
|
138
|
+
return response;
|
|
139
|
+
}
|
|
140
|
+
function clearSessionCookieOnResponse(response, options = {}) {
|
|
141
|
+
const cookieName = options.cookieName ?? "stackwright_session";
|
|
142
|
+
response.cookies.delete(cookieName);
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
120
145
|
export {
|
|
121
146
|
AuthContext,
|
|
122
147
|
AuthProvider,
|
|
123
148
|
RBACEngine,
|
|
124
149
|
clearSessionCookie,
|
|
150
|
+
clearSessionCookieOnResponse,
|
|
151
|
+
createAuthHandler,
|
|
125
152
|
createAuthMiddleware,
|
|
153
|
+
createAuthProxy,
|
|
126
154
|
protectedRoute,
|
|
155
|
+
protectedRouteHandler,
|
|
127
156
|
setSessionCookie,
|
|
157
|
+
setSessionCookieOnResponse,
|
|
128
158
|
useAuth,
|
|
129
159
|
useRequireAuth
|
|
130
160
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 { createAuthHandler, type AuthHandlerConfig } from './auth-handler.js';\n\n/**\n * Configuration for Next.js middleware-based auth (Next.js <16).\n *\n * @deprecated For Next.js >=16, use {@link ProxyConfig} with {@link createAuthProxy} instead.\n * The `middleware.ts` file convention is deprecated in Next.js 16.\n */\nexport type MiddlewareConfig = AuthHandlerConfig;\n\n/**\n * Create Next.js middleware for authentication and authorization.\n *\n * For Next.js 14–15 projects that use the `middleware.ts` file convention.\n *\n * @deprecated For Next.js >=16, use {@link createAuthProxy} instead.\n * The `middleware.ts` file convention is deprecated in Next.js 16 in favor of `proxy.ts`.\n *\n * @example\n * ```typescript\n * // middleware.ts (Next.js 14–15)\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 return createAuthHandler(config);\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 { createAuthProxy, type ProxyConfig } from './proxy.js';\nexport { createAuthHandler, type AuthHandlerConfig } from './auth-handler.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":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as next_server_js from 'next/server.js';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { AuthConfig, SessionManager, RBACEngine } from '@stackwright-pro/auth';
|
|
4
|
+
|
|
5
|
+
interface AuthHandlerConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Auth configuration
|
|
8
|
+
*/
|
|
9
|
+
authConfig: AuthConfig;
|
|
10
|
+
/**
|
|
11
|
+
* Session manager instance
|
|
12
|
+
*/
|
|
13
|
+
sessionManager: SessionManager;
|
|
14
|
+
/**
|
|
15
|
+
* RBAC engine instance
|
|
16
|
+
*/
|
|
17
|
+
rbacEngine: RBACEngine;
|
|
18
|
+
/**
|
|
19
|
+
* Cookie name for session (default: 'stackwright_session')
|
|
20
|
+
*/
|
|
21
|
+
cookieName?: string;
|
|
22
|
+
/**
|
|
23
|
+
* URL to redirect to when authentication fails (default: '/login')
|
|
24
|
+
*/
|
|
25
|
+
loginUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* URL to redirect to when authorization fails (default: '/unauthorized')
|
|
28
|
+
*/
|
|
29
|
+
unauthorizedUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Core auth handler shared by both the middleware (Next.js <16) and
|
|
33
|
+
* proxy (Next.js >=16) conventions.
|
|
34
|
+
*
|
|
35
|
+
* @internal — consumers should use `createAuthMiddleware` or `createAuthProxy`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createAuthHandler(config: AuthHandlerConfig): (request: NextRequest) => Promise<NextResponse | undefined>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for Next.js proxy-based auth (Next.js >=16).
|
|
41
|
+
*/
|
|
42
|
+
type ProxyConfig = AuthHandlerConfig;
|
|
43
|
+
/**
|
|
44
|
+
* Create a Next.js proxy handler for authentication and authorization.
|
|
45
|
+
*
|
|
46
|
+
* For Next.js >=16 projects that use the `proxy.ts` file convention,
|
|
47
|
+
* replacing the deprecated `middleware.ts` convention.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // proxy.ts (Next.js >=16)
|
|
52
|
+
* import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
53
|
+
*
|
|
54
|
+
* export default createAuthProxy({
|
|
55
|
+
* authConfig,
|
|
56
|
+
* sessionManager,
|
|
57
|
+
* rbacEngine,
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* export const config = {
|
|
61
|
+
* matcher: ['/((?!_next|api/auth).*)'],
|
|
62
|
+
* };
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function createAuthProxy(config: ProxyConfig): (request: next_server_js.NextRequest) => Promise<next_server_js.NextResponse | undefined>;
|
|
66
|
+
|
|
67
|
+
export { type AuthHandlerConfig as A, type ProxyConfig as P, createAuthProxy as a, createAuthHandler as c };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as next_server_js from 'next/server.js';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { AuthConfig, SessionManager, RBACEngine } from '@stackwright-pro/auth';
|
|
4
|
+
|
|
5
|
+
interface AuthHandlerConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Auth configuration
|
|
8
|
+
*/
|
|
9
|
+
authConfig: AuthConfig;
|
|
10
|
+
/**
|
|
11
|
+
* Session manager instance
|
|
12
|
+
*/
|
|
13
|
+
sessionManager: SessionManager;
|
|
14
|
+
/**
|
|
15
|
+
* RBAC engine instance
|
|
16
|
+
*/
|
|
17
|
+
rbacEngine: RBACEngine;
|
|
18
|
+
/**
|
|
19
|
+
* Cookie name for session (default: 'stackwright_session')
|
|
20
|
+
*/
|
|
21
|
+
cookieName?: string;
|
|
22
|
+
/**
|
|
23
|
+
* URL to redirect to when authentication fails (default: '/login')
|
|
24
|
+
*/
|
|
25
|
+
loginUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* URL to redirect to when authorization fails (default: '/unauthorized')
|
|
28
|
+
*/
|
|
29
|
+
unauthorizedUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Core auth handler shared by both the middleware (Next.js <16) and
|
|
33
|
+
* proxy (Next.js >=16) conventions.
|
|
34
|
+
*
|
|
35
|
+
* @internal — consumers should use `createAuthMiddleware` or `createAuthProxy`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createAuthHandler(config: AuthHandlerConfig): (request: NextRequest) => Promise<NextResponse | undefined>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for Next.js proxy-based auth (Next.js >=16).
|
|
41
|
+
*/
|
|
42
|
+
type ProxyConfig = AuthHandlerConfig;
|
|
43
|
+
/**
|
|
44
|
+
* Create a Next.js proxy handler for authentication and authorization.
|
|
45
|
+
*
|
|
46
|
+
* For Next.js >=16 projects that use the `proxy.ts` file convention,
|
|
47
|
+
* replacing the deprecated `middleware.ts` convention.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // proxy.ts (Next.js >=16)
|
|
52
|
+
* import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
53
|
+
*
|
|
54
|
+
* export default createAuthProxy({
|
|
55
|
+
* authConfig,
|
|
56
|
+
* sessionManager,
|
|
57
|
+
* rbacEngine,
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* export const config = {
|
|
61
|
+
* matcher: ['/((?!_next|api/auth).*)'],
|
|
62
|
+
* };
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function createAuthProxy(config: ProxyConfig): (request: next_server_js.NextRequest) => Promise<next_server_js.NextResponse | undefined>;
|
|
66
|
+
|
|
67
|
+
export { type AuthHandlerConfig as A, type ProxyConfig as P, createAuthProxy as a, createAuthHandler as c };
|
package/dist/proxy.d.mts
ADDED
package/dist/proxy.d.ts
ADDED
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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/proxy.ts
|
|
21
|
+
var proxy_exports = {};
|
|
22
|
+
__export(proxy_exports, {
|
|
23
|
+
createAuthProxy: () => createAuthProxy
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(proxy_exports);
|
|
26
|
+
|
|
27
|
+
// src/auth-handler.ts
|
|
28
|
+
var import_server = require("next/server");
|
|
29
|
+
function createAuthHandler(config) {
|
|
30
|
+
const {
|
|
31
|
+
sessionManager,
|
|
32
|
+
rbacEngine,
|
|
33
|
+
cookieName = "stackwright_session",
|
|
34
|
+
loginUrl = "/login",
|
|
35
|
+
unauthorizedUrl = "/unauthorized"
|
|
36
|
+
} = config;
|
|
37
|
+
return async function authHandler(request) {
|
|
38
|
+
const { pathname } = request.nextUrl;
|
|
39
|
+
if (rbacEngine.isPublicRoute(pathname)) {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
const sessionCookie = request.cookies.get(cookieName);
|
|
43
|
+
let session = null;
|
|
44
|
+
if (sessionCookie) {
|
|
45
|
+
session = await sessionManager.deserialize(sessionCookie.value);
|
|
46
|
+
}
|
|
47
|
+
if (!session || sessionManager.isExpired(session)) {
|
|
48
|
+
const url = request.nextUrl.clone();
|
|
49
|
+
url.pathname = loginUrl;
|
|
50
|
+
url.searchParams.set("redirect", pathname);
|
|
51
|
+
return import_server.NextResponse.redirect(url);
|
|
52
|
+
}
|
|
53
|
+
if (!rbacEngine.canAccessRoute(session.user, pathname)) {
|
|
54
|
+
const url = request.nextUrl.clone();
|
|
55
|
+
url.pathname = unauthorizedUrl;
|
|
56
|
+
return import_server.NextResponse.redirect(url);
|
|
57
|
+
}
|
|
58
|
+
if (sessionManager.shouldRefresh(session)) {
|
|
59
|
+
const refreshed = await sessionManager.refreshSession(session);
|
|
60
|
+
const newJwt = await sessionManager.serialize(refreshed);
|
|
61
|
+
const response = import_server.NextResponse.next();
|
|
62
|
+
response.cookies.set(cookieName, newJwt, {
|
|
63
|
+
httpOnly: true,
|
|
64
|
+
secure: true,
|
|
65
|
+
sameSite: "lax",
|
|
66
|
+
maxAge: 900
|
|
67
|
+
// 15 minutes
|
|
68
|
+
});
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/proxy.ts
|
|
76
|
+
function createAuthProxy(config) {
|
|
77
|
+
return createAuthHandler(config);
|
|
78
|
+
}
|
|
79
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
80
|
+
0 && (module.exports = {
|
|
81
|
+
createAuthProxy
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/proxy.ts","../src/auth-handler.ts"],"sourcesContent":["import { createAuthHandler, type AuthHandlerConfig } from './auth-handler.js';\n\n/**\n * Configuration for Next.js proxy-based auth (Next.js >=16).\n */\nexport type ProxyConfig = AuthHandlerConfig;\n\n/**\n * Create a Next.js proxy handler for authentication and authorization.\n *\n * For Next.js >=16 projects that use the `proxy.ts` file convention,\n * replacing the deprecated `middleware.ts` convention.\n *\n * @example\n * ```typescript\n * // proxy.ts (Next.js >=16)\n * import { createAuthProxy } from '@stackwright-pro/auth-nextjs';\n *\n * export default createAuthProxy({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n *\n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthProxy(config: ProxyConfig) {\n return createAuthHandler(config);\n}\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 AuthHandlerConfig {\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 * Core auth handler shared by both the middleware (Next.js <16) and\n * proxy (Next.js >=16) conventions.\n *\n * @internal — consumers should use `createAuthMiddleware` or `createAuthProxy`.\n */\nexport function createAuthHandler(config: AuthHandlerConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n\n return async function authHandler(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 handler\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAA+C;AA8CxC,SAAS,kBAAkB,QAA2B;AAC3D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB,IAAI;AAEJ,SAAO,eAAe,YAAY,SAAyD;AACzF,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAI,WAAW,cAAc,QAAQ,GAAG;AACtC,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,QAAQ,QAAQ,IAAI,UAAU;AACpD,QAAI,UAA8B;AAElC,QAAI,eAAe;AACjB,gBAAU,MAAM,eAAe,YAAY,cAAc,KAAK;AAAA,IAChE;AAGA,QAAI,CAAC,WAAW,eAAe,UAAU,OAAO,GAAG;AAEjD,YAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,UAAI,WAAW;AACf,UAAI,aAAa,IAAI,YAAY,QAAQ;AACzC,aAAO,2BAAa,SAAS,GAAG;AAAA,IAClC;AAGA,QAAI,CAAC,WAAW,eAAe,QAAQ,MAAM,QAAQ,GAAG;AAEtD,YAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,UAAI,WAAW;AACf,aAAO,2BAAa,SAAS,GAAG;AAAA,IAClC;AAGA,QAAI,eAAe,cAAc,OAAO,GAAG;AACzC,YAAM,YAAY,MAAM,eAAe,eAAe,OAAO;AAC7D,YAAM,SAAS,MAAM,eAAe,UAAU,SAAS;AAEvD,YAAM,WAAW,2BAAa,KAAK;AACnC,eAAS,QAAQ,IAAI,YAAY,QAAQ;AAAA,QACvC,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,QAAQ;AAAA;AAAA,MACV,CAAC;AAED,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AACF;;;AD9EO,SAAS,gBAAgB,QAAqB;AACnD,SAAO,kBAAkB,MAAM;AACjC;","names":[]}
|
package/dist/proxy.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackwright-pro/auth-nextjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-alpha.0",
|
|
4
4
|
"description": "Next.js adapter for Stackwright Pro authentication",
|
|
5
|
-
"license": "
|
|
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",
|
|
@@ -11,19 +11,25 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.mjs",
|
|
13
13
|
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./proxy": {
|
|
16
|
+
"types": "./dist/proxy.d.ts",
|
|
17
|
+
"import": "./dist/proxy.mjs",
|
|
18
|
+
"require": "./dist/proxy.js"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"files": [
|
|
17
22
|
"dist"
|
|
18
23
|
],
|
|
19
24
|
"dependencies": {
|
|
20
|
-
"@stackwright-pro/auth": "0.2.0-alpha.
|
|
25
|
+
"@stackwright-pro/auth": "0.2.0-alpha.11"
|
|
21
26
|
},
|
|
22
27
|
"devDependencies": {
|
|
23
|
-
"@types/node": "^25.
|
|
28
|
+
"@types/node": "^25.9.2",
|
|
29
|
+
"reflect-metadata": "^0.2.2",
|
|
24
30
|
"tsup": "^8.5.1",
|
|
25
|
-
"typescript": "^
|
|
26
|
-
"vitest": "^4.
|
|
31
|
+
"typescript": "^6.0.3",
|
|
32
|
+
"vitest": "^4.1.8"
|
|
27
33
|
},
|
|
28
34
|
"peerDependencies": {
|
|
29
35
|
"next": ">=14.0.0 <16.0.0-beta.0 || >=16.2.3",
|