@stackwright-pro/auth-nextjs 0.1.0 → 0.1.1-alpha.1
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/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +43 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { AuthConfig, SessionManager, RBACEngine, AuthSession, CookieOptions } from '@stackwright-pro/auth';
|
|
3
|
-
export { AuthConfig, AuthSession, AuthUser } from '@stackwright-pro/auth';
|
|
3
|
+
export { AuthConfig, AuthContext, AuthContextValue, AuthProvider, AuthProviderProps, AuthSession, AuthUser, RBACEngine, useAuth, useRequireAuth } from '@stackwright-pro/auth';
|
|
4
4
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
5
|
|
|
6
6
|
interface MiddlewareConfig {
|
|
@@ -67,6 +67,12 @@ interface ProtectedRouteConfig {
|
|
|
67
67
|
* Cookie name (default: 'stackwright_session')
|
|
68
68
|
*/
|
|
69
69
|
cookieName?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Optional RBAC engine for wildcard-aware role/permission checking.
|
|
72
|
+
* When provided, authorization decisions use RBACEngine (consistent
|
|
73
|
+
* with middleware). Without it, falls back to direct array checks.
|
|
74
|
+
*/
|
|
75
|
+
rbacEngine?: RBACEngine;
|
|
70
76
|
}
|
|
71
77
|
/**
|
|
72
78
|
* Handler function with authenticated session
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { AuthConfig, SessionManager, RBACEngine, AuthSession, CookieOptions } from '@stackwright-pro/auth';
|
|
3
|
-
export { AuthConfig, AuthSession, AuthUser } from '@stackwright-pro/auth';
|
|
3
|
+
export { AuthConfig, AuthContext, AuthContextValue, AuthProvider, AuthProviderProps, AuthSession, AuthUser, RBACEngine, useAuth, useRequireAuth } from '@stackwright-pro/auth';
|
|
4
4
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
5
|
|
|
6
6
|
interface MiddlewareConfig {
|
|
@@ -67,6 +67,12 @@ interface ProtectedRouteConfig {
|
|
|
67
67
|
* Cookie name (default: 'stackwright_session')
|
|
68
68
|
*/
|
|
69
69
|
cookieName?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Optional RBAC engine for wildcard-aware role/permission checking.
|
|
72
|
+
* When provided, authorization decisions use RBACEngine (consistent
|
|
73
|
+
* with middleware). Without it, falls back to direct array checks.
|
|
74
|
+
*/
|
|
75
|
+
rbacEngine?: RBACEngine;
|
|
70
76
|
}
|
|
71
77
|
/**
|
|
72
78
|
* Handler function with authenticated session
|
package/dist/index.js
CHANGED
|
@@ -20,10 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AuthContext: () => import_auth3.AuthContext,
|
|
24
|
+
AuthProvider: () => import_auth3.AuthProvider,
|
|
25
|
+
RBACEngine: () => import_auth2.RBACEngine,
|
|
23
26
|
clearSessionCookie: () => clearSessionCookie,
|
|
24
27
|
createAuthMiddleware: () => createAuthMiddleware,
|
|
25
28
|
protectedRoute: () => protectedRoute,
|
|
26
|
-
setSessionCookie: () => setSessionCookie
|
|
29
|
+
setSessionCookie: () => setSessionCookie,
|
|
30
|
+
useAuth: () => import_auth3.useAuth,
|
|
31
|
+
useRequireAuth: () => import_auth3.useRequireAuth
|
|
27
32
|
});
|
|
28
33
|
module.exports = __toCommonJS(index_exports);
|
|
29
34
|
|
|
@@ -64,7 +69,7 @@ function createAuthMiddleware(config) {
|
|
|
64
69
|
const response = import_server.NextResponse.next();
|
|
65
70
|
response.cookies.set(cookieName, newJwt, {
|
|
66
71
|
httpOnly: true,
|
|
67
|
-
secure:
|
|
72
|
+
secure: true,
|
|
68
73
|
sameSite: "lax",
|
|
69
74
|
maxAge: 900
|
|
70
75
|
// 15 minutes
|
|
@@ -77,12 +82,7 @@ function createAuthMiddleware(config) {
|
|
|
77
82
|
|
|
78
83
|
// src/api-helpers.ts
|
|
79
84
|
function protectedRoute(handler, config) {
|
|
80
|
-
const {
|
|
81
|
-
sessionManager,
|
|
82
|
-
roles,
|
|
83
|
-
permissions,
|
|
84
|
-
cookieName = "stackwright_session"
|
|
85
|
-
} = config;
|
|
85
|
+
const { sessionManager, roles, permissions, cookieName = "stackwright_session" } = config;
|
|
86
86
|
return async (req, res) => {
|
|
87
87
|
const sessionCookie = req.cookies[cookieName];
|
|
88
88
|
if (!sessionCookie) {
|
|
@@ -95,19 +95,33 @@ function protectedRoute(handler, config) {
|
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
97
|
if (roles && roles.length > 0) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
if (config.rbacEngine) {
|
|
99
|
+
if (!config.rbacEngine.hasAnyRole(session.user, roles)) {
|
|
100
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const hasRole = roles.some((role) => session.user.roles.includes(role));
|
|
105
|
+
if (!hasRole) {
|
|
106
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
if (permissions && permissions.length > 0) {
|
|
105
|
-
|
|
106
|
-
(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
if (config.rbacEngine) {
|
|
113
|
+
if (!config.rbacEngine.hasAllPermissions(session.user, permissions)) {
|
|
114
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
const hasAllPermissions = permissions.every(
|
|
119
|
+
(permission) => session.user.permissions?.includes(permission)
|
|
120
|
+
);
|
|
121
|
+
if (!hasAllPermissions) {
|
|
122
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
111
125
|
}
|
|
112
126
|
}
|
|
113
127
|
await handler(req, res, session);
|
|
@@ -123,7 +137,7 @@ function setSessionCookie(res, sessionJwt, options = {}) {
|
|
|
123
137
|
...options,
|
|
124
138
|
maxAge,
|
|
125
139
|
httpOnly: true,
|
|
126
|
-
secure:
|
|
140
|
+
secure: true,
|
|
127
141
|
sameSite: "lax"
|
|
128
142
|
});
|
|
129
143
|
res.setHeader("Set-Cookie", cookie);
|
|
@@ -133,11 +147,20 @@ function clearSessionCookie(res, options = {}) {
|
|
|
133
147
|
const cookie = (0, import_auth.clearCookie)(cookieName, options);
|
|
134
148
|
res.setHeader("Set-Cookie", cookie);
|
|
135
149
|
}
|
|
150
|
+
|
|
151
|
+
// src/index.ts
|
|
152
|
+
var import_auth2 = require("@stackwright-pro/auth");
|
|
153
|
+
var import_auth3 = require("@stackwright-pro/auth");
|
|
136
154
|
// Annotate the CommonJS export names for ESM import in node:
|
|
137
155
|
0 && (module.exports = {
|
|
156
|
+
AuthContext,
|
|
157
|
+
AuthProvider,
|
|
158
|
+
RBACEngine,
|
|
138
159
|
clearSessionCookie,
|
|
139
160
|
createAuthMiddleware,
|
|
140
161
|
protectedRoute,
|
|
141
|
-
setSessionCookie
|
|
162
|
+
setSessionCookie,
|
|
163
|
+
useAuth,
|
|
164
|
+
useRequireAuth
|
|
142
165
|
});
|
|
143
166
|
//# sourceMappingURL=index.js.map
|
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 from core\nexport type { \n AuthUser, \n AuthSession, \n AuthConfig \n} from '@stackwright-pro/auth';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport { SessionManager, RBACEngine, type AuthConfig, type AuthSession } from '@stackwright-pro/auth';\n\nexport interface MiddlewareConfig {\n /**\n * Auth configuration\n */\n authConfig: AuthConfig;\n \n /**\n * Session manager instance\n */\n sessionManager: SessionManager;\n \n /**\n * RBAC engine instance\n */\n rbacEngine: RBACEngine;\n \n /**\n * Cookie name for session (default: 'stackwright_session')\n */\n cookieName?: string;\n \n /**\n * URL to redirect to when authentication fails (default: '/login')\n */\n loginUrl?: string;\n \n /**\n * URL to redirect to when authorization fails (default: '/unauthorized')\n */\n unauthorizedUrl?: string;\n}\n\n/**\n * Create Next.js middleware for authentication and authorization\n * \n * @example\n * ```typescript\n * // middleware.ts\n * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';\n * \n * export default createAuthMiddleware({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n * \n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthMiddleware(config: MiddlewareConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n \n return async function authMiddleware(\n request: NextRequest\n ): Promise<NextResponse | undefined> {\n const { pathname } = request.nextUrl;\n \n // Check if route is public\n if (rbacEngine.isPublicRoute(pathname)) {\n return undefined; // Continue to next middleware\n }\n \n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n let session: AuthSession | null = null;\n \n if (sessionCookie) {\n session = await sessionManager.deserialize(sessionCookie.value);\n }\n \n // Check if session is valid\n if (!session || sessionManager.isExpired(session)) {\n // Redirect to login\n const url = request.nextUrl.clone();\n url.pathname = loginUrl;\n url.searchParams.set('redirect', pathname);\n return NextResponse.redirect(url);\n }\n \n // Check if user can access route\n if (!rbacEngine.canAccessRoute(session.user, pathname)) {\n // Redirect to unauthorized\n const url = request.nextUrl.clone();\n url.pathname = unauthorizedUrl;\n return NextResponse.redirect(url);\n }\n \n // Refresh session if needed\n if (sessionManager.shouldRefresh(session)) {\n const refreshed = await sessionManager.refreshSession(session);\n const newJwt = await sessionManager.serialize(refreshed);\n \n const response = NextResponse.next();\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 900, // 15 minutes\n });\n \n return response;\n }\n \n // Allow request to continue\n return undefined;\n };\n}\n","import type { NextApiRequest, NextApiResponse } from 'next';\nimport { SessionManager, type AuthSession, type AuthUser } from '@stackwright-pro/auth';\n\nexport interface ProtectedRouteConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n \n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n \n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n \n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n}\n\n/**\n * Handler function with authenticated session\n */\nexport type AuthenticatedHandler<T = any> = (\n req: NextApiRequest,\n res: NextApiResponse<T>,\n session: AuthSession\n) => void | Promise<void>;\n\n/**\n * Wrap API route with authentication/authorization\n * \n * @example\n * ```typescript\n * // pages/api/equipment/[id].ts\n * export default protectedRoute(\n * async (req, res, session) => {\n * const equipment = await getEquipment(req.query.id);\n * res.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRoute<T = any>(\n handler: AuthenticatedHandler<T>,\n config: ProtectedRouteConfig\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const {\n sessionManager,\n roles,\n permissions,\n cookieName = 'stackwright_session',\n } = config;\n \n return async (req: NextApiRequest, res: NextApiResponse) => {\n // Extract session from cookie\n const sessionCookie = req.cookies[cookieName];\n \n if (!sessionCookie) {\n res.status(401).json({ error: 'Not authenticated' } as any);\n return;\n }\n \n // Verify session\n const session = await sessionManager.deserialize(sessionCookie);\n \n if (!session || sessionManager.isExpired(session)) {\n res.status(401).json({ error: 'Session expired' } as any);\n return;\n }\n \n // Check roles\n if (roles && roles.length > 0) {\n const hasRole = roles.some(role => session.user.roles.includes(role));\n if (!hasRole) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n \n // Check permissions\n if (permissions && permissions.length > 0) {\n const hasAllPermissions = permissions.every(permission =>\n session.user.permissions?.includes(permission)\n );\n if (!hasAllPermissions) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n \n // Call handler with session\n await handler(req, res, session);\n };\n}\n","import type { NextApiResponse } from 'next';\nimport { serializeCookie, clearCookie, type CookieOptions } from '@stackwright-pro/auth';\n\n/**\n * Set session cookie in Next.js API response\n */\nexport function setSessionCookie(\n res: NextApiResponse,\n sessionJwt: string,\n options: CookieOptions = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n const maxAge = options.maxAge || 900; // 15 minutes default\n \n const cookie = serializeCookie(cookieName, sessionJwt, {\n ...options,\n maxAge,\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n });\n \n res.setHeader('Set-Cookie', cookie);\n}\n\n/**\n * Clear session cookie in Next.js API response\n */\nexport function clearSessionCookie(\n res: NextApiResponse,\n options: Pick<CookieOptions, 'name' | 'domain' | 'path'> = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n \n const cookie = clearCookie(cookieName, options);\n \n res.setHeader('Set-Cookie', cookie);\n}\n"],"mappings":"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","names":[]}
|
|
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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ function createAuthMiddleware(config) {
|
|
|
35
35
|
const response = NextResponse.next();
|
|
36
36
|
response.cookies.set(cookieName, newJwt, {
|
|
37
37
|
httpOnly: true,
|
|
38
|
-
secure:
|
|
38
|
+
secure: true,
|
|
39
39
|
sameSite: "lax",
|
|
40
40
|
maxAge: 900
|
|
41
41
|
// 15 minutes
|
|
@@ -48,12 +48,7 @@ function createAuthMiddleware(config) {
|
|
|
48
48
|
|
|
49
49
|
// src/api-helpers.ts
|
|
50
50
|
function protectedRoute(handler, config) {
|
|
51
|
-
const {
|
|
52
|
-
sessionManager,
|
|
53
|
-
roles,
|
|
54
|
-
permissions,
|
|
55
|
-
cookieName = "stackwright_session"
|
|
56
|
-
} = config;
|
|
51
|
+
const { sessionManager, roles, permissions, cookieName = "stackwright_session" } = config;
|
|
57
52
|
return async (req, res) => {
|
|
58
53
|
const sessionCookie = req.cookies[cookieName];
|
|
59
54
|
if (!sessionCookie) {
|
|
@@ -66,19 +61,33 @@ function protectedRoute(handler, config) {
|
|
|
66
61
|
return;
|
|
67
62
|
}
|
|
68
63
|
if (roles && roles.length > 0) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
if (config.rbacEngine) {
|
|
65
|
+
if (!config.rbacEngine.hasAnyRole(session.user, roles)) {
|
|
66
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const hasRole = roles.some((role) => session.user.roles.includes(role));
|
|
71
|
+
if (!hasRole) {
|
|
72
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
if (permissions && permissions.length > 0) {
|
|
76
|
-
|
|
77
|
-
(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if (config.rbacEngine) {
|
|
79
|
+
if (!config.rbacEngine.hasAllPermissions(session.user, permissions)) {
|
|
80
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const hasAllPermissions = permissions.every(
|
|
85
|
+
(permission) => session.user.permissions?.includes(permission)
|
|
86
|
+
);
|
|
87
|
+
if (!hasAllPermissions) {
|
|
88
|
+
res.status(403).json({ error: "Insufficient permissions" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
await handler(req, res, session);
|
|
@@ -94,7 +103,7 @@ function setSessionCookie(res, sessionJwt, options = {}) {
|
|
|
94
103
|
...options,
|
|
95
104
|
maxAge,
|
|
96
105
|
httpOnly: true,
|
|
97
|
-
secure:
|
|
106
|
+
secure: true,
|
|
98
107
|
sameSite: "lax"
|
|
99
108
|
});
|
|
100
109
|
res.setHeader("Set-Cookie", cookie);
|
|
@@ -104,10 +113,19 @@ function clearSessionCookie(res, options = {}) {
|
|
|
104
113
|
const cookie = clearCookie(cookieName, options);
|
|
105
114
|
res.setHeader("Set-Cookie", cookie);
|
|
106
115
|
}
|
|
116
|
+
|
|
117
|
+
// src/index.ts
|
|
118
|
+
import { RBACEngine } from "@stackwright-pro/auth";
|
|
119
|
+
import { AuthProvider, AuthContext, useAuth, useRequireAuth } from "@stackwright-pro/auth";
|
|
107
120
|
export {
|
|
121
|
+
AuthContext,
|
|
122
|
+
AuthProvider,
|
|
123
|
+
RBACEngine,
|
|
108
124
|
clearSessionCookie,
|
|
109
125
|
createAuthMiddleware,
|
|
110
126
|
protectedRoute,
|
|
111
|
-
setSessionCookie
|
|
127
|
+
setSessionCookie,
|
|
128
|
+
useAuth,
|
|
129
|
+
useRequireAuth
|
|
112
130
|
};
|
|
113
131
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/api-helpers.ts","../src/cookie-utils.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\nimport { SessionManager, RBACEngine, type AuthConfig, type AuthSession } from '@stackwright-pro/auth';\n\nexport interface MiddlewareConfig {\n /**\n * Auth configuration\n */\n authConfig: AuthConfig;\n \n /**\n * Session manager instance\n */\n sessionManager: SessionManager;\n \n /**\n * RBAC engine instance\n */\n rbacEngine: RBACEngine;\n \n /**\n * Cookie name for session (default: 'stackwright_session')\n */\n cookieName?: string;\n \n /**\n * URL to redirect to when authentication fails (default: '/login')\n */\n loginUrl?: string;\n \n /**\n * URL to redirect to when authorization fails (default: '/unauthorized')\n */\n unauthorizedUrl?: string;\n}\n\n/**\n * Create Next.js middleware for authentication and authorization\n * \n * @example\n * ```typescript\n * // middleware.ts\n * import { createAuthMiddleware } from '@stackwright-pro/auth-nextjs';\n * \n * export default createAuthMiddleware({\n * authConfig,\n * sessionManager,\n * rbacEngine,\n * });\n * \n * export const config = {\n * matcher: ['/((?!_next|api/auth).*)'],\n * };\n * ```\n */\nexport function createAuthMiddleware(config: MiddlewareConfig) {\n const {\n sessionManager,\n rbacEngine,\n cookieName = 'stackwright_session',\n loginUrl = '/login',\n unauthorizedUrl = '/unauthorized',\n } = config;\n \n return async function authMiddleware(\n request: NextRequest\n ): Promise<NextResponse | undefined> {\n const { pathname } = request.nextUrl;\n \n // Check if route is public\n if (rbacEngine.isPublicRoute(pathname)) {\n return undefined; // Continue to next middleware\n }\n \n // Extract session from cookie\n const sessionCookie = request.cookies.get(cookieName);\n let session: AuthSession | null = null;\n \n if (sessionCookie) {\n session = await sessionManager.deserialize(sessionCookie.value);\n }\n \n // Check if session is valid\n if (!session || sessionManager.isExpired(session)) {\n // Redirect to login\n const url = request.nextUrl.clone();\n url.pathname = loginUrl;\n url.searchParams.set('redirect', pathname);\n return NextResponse.redirect(url);\n }\n \n // Check if user can access route\n if (!rbacEngine.canAccessRoute(session.user, pathname)) {\n // Redirect to unauthorized\n const url = request.nextUrl.clone();\n url.pathname = unauthorizedUrl;\n return NextResponse.redirect(url);\n }\n \n // Refresh session if needed\n if (sessionManager.shouldRefresh(session)) {\n const refreshed = await sessionManager.refreshSession(session);\n const newJwt = await sessionManager.serialize(refreshed);\n \n const response = NextResponse.next();\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 900, // 15 minutes\n });\n \n return response;\n }\n \n // Allow request to continue\n return undefined;\n };\n}\n","import type { NextApiRequest, NextApiResponse } from 'next';\nimport { SessionManager, type AuthSession, type AuthUser } from '@stackwright-pro/auth';\n\nexport interface ProtectedRouteConfig {\n /**\n * Session manager for validating sessions\n */\n sessionManager: SessionManager;\n \n /**\n * Required roles (user must have ANY of these)\n */\n roles?: string[];\n \n /**\n * Required permissions (user must have ALL of these)\n */\n permissions?: string[];\n \n /**\n * Cookie name (default: 'stackwright_session')\n */\n cookieName?: string;\n}\n\n/**\n * Handler function with authenticated session\n */\nexport type AuthenticatedHandler<T = any> = (\n req: NextApiRequest,\n res: NextApiResponse<T>,\n session: AuthSession\n) => void | Promise<void>;\n\n/**\n * Wrap API route with authentication/authorization\n * \n * @example\n * ```typescript\n * // pages/api/equipment/[id].ts\n * export default protectedRoute(\n * async (req, res, session) => {\n * const equipment = await getEquipment(req.query.id);\n * res.json(equipment);\n * },\n * {\n * sessionManager,\n * roles: ['ANALYST', 'ADMIN'],\n * }\n * );\n * ```\n */\nexport function protectedRoute<T = any>(\n handler: AuthenticatedHandler<T>,\n config: ProtectedRouteConfig\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const {\n sessionManager,\n roles,\n permissions,\n cookieName = 'stackwright_session',\n } = config;\n \n return async (req: NextApiRequest, res: NextApiResponse) => {\n // Extract session from cookie\n const sessionCookie = req.cookies[cookieName];\n \n if (!sessionCookie) {\n res.status(401).json({ error: 'Not authenticated' } as any);\n return;\n }\n \n // Verify session\n const session = await sessionManager.deserialize(sessionCookie);\n \n if (!session || sessionManager.isExpired(session)) {\n res.status(401).json({ error: 'Session expired' } as any);\n return;\n }\n \n // Check roles\n if (roles && roles.length > 0) {\n const hasRole = roles.some(role => session.user.roles.includes(role));\n if (!hasRole) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n \n // Check permissions\n if (permissions && permissions.length > 0) {\n const hasAllPermissions = permissions.every(permission =>\n session.user.permissions?.includes(permission)\n );\n if (!hasAllPermissions) {\n res.status(403).json({ error: 'Insufficient permissions' } as any);\n return;\n }\n }\n \n // Call handler with session\n await handler(req, res, session);\n };\n}\n","import type { NextApiResponse } from 'next';\nimport { serializeCookie, clearCookie, type CookieOptions } from '@stackwright-pro/auth';\n\n/**\n * Set session cookie in Next.js API response\n */\nexport function setSessionCookie(\n res: NextApiResponse,\n sessionJwt: string,\n options: CookieOptions = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n const maxAge = options.maxAge || 900; // 15 minutes default\n \n const cookie = serializeCookie(cookieName, sessionJwt, {\n ...options,\n maxAge,\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n });\n \n res.setHeader('Set-Cookie', cookie);\n}\n\n/**\n * Clear session cookie in Next.js API response\n */\nexport function clearSessionCookie(\n res: NextApiResponse,\n options: Pick<CookieOptions, 'name' | 'domain' | 'path'> = {}\n): void {\n const cookieName = options.name || 'stackwright_session';\n \n const cookie = clearCookie(cookieName, options);\n \n res.setHeader('Set-Cookie', cookie);\n}\n"],"mappings":"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","names":[]}
|
|
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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackwright-pro/auth-nextjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-alpha.1",
|
|
4
4
|
"description": "Next.js adapter for Stackwright Pro authentication",
|
|
5
5
|
"license": "PROPRIETARY",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@stackwright-pro/auth": "0.1.
|
|
20
|
+
"@stackwright-pro/auth": "0.1.1-alpha.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^25.4.0",
|
|
@@ -30,10 +30,14 @@
|
|
|
30
30
|
"react": "^19",
|
|
31
31
|
"react-dom": "^19"
|
|
32
32
|
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
33
36
|
"scripts": {
|
|
34
37
|
"build": "tsup",
|
|
35
38
|
"dev": "tsup --watch",
|
|
36
39
|
"test": "vitest run",
|
|
40
|
+
"test:coverage": "vitest run --coverage",
|
|
37
41
|
"test:watch": "vitest"
|
|
38
42
|
}
|
|
39
43
|
}
|