@spfn/auth 0.2.0-beta.11 → 0.2.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +338 -8
- package/dist/{authenticate-CU6_zQaa.d.ts → authenticate-Cz2FjLdB.d.ts} +113 -1
- package/dist/config.d.ts +120 -0
- package/dist/config.js +72 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +46 -2
- package/dist/nextjs/api.js +186 -0
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/client.js +80 -0
- package/dist/nextjs/client.js.map +1 -0
- package/dist/nextjs/server.d.ts +68 -2
- package/dist/nextjs/server.js +125 -2
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +243 -3
- package/dist/server.js +594 -23
- package/dist/server.js.map +1 -1
- package/package.json +10 -3
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/nextjs/components/oauth-callback.tsx
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function OAuthCallback({
|
|
7
|
+
apiBasePath = "/api/rpc",
|
|
8
|
+
loadingComponent,
|
|
9
|
+
errorComponent,
|
|
10
|
+
onSuccess,
|
|
11
|
+
onError
|
|
12
|
+
}) {
|
|
13
|
+
const [error, setError] = useState(null);
|
|
14
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
async function finalizeOAuth() {
|
|
17
|
+
try {
|
|
18
|
+
const params = new URLSearchParams(window.location.search);
|
|
19
|
+
const userId = params.get("userId");
|
|
20
|
+
const keyId = params.get("keyId");
|
|
21
|
+
const returnUrl = params.get("returnUrl") || "/";
|
|
22
|
+
const errorParam = params.get("error");
|
|
23
|
+
if (errorParam) {
|
|
24
|
+
throw new Error(errorParam);
|
|
25
|
+
}
|
|
26
|
+
if (!userId || !keyId) {
|
|
27
|
+
throw new Error("Missing required parameters");
|
|
28
|
+
}
|
|
29
|
+
const response = await fetch(`${apiBasePath}/oauthFinalize`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json"
|
|
33
|
+
},
|
|
34
|
+
credentials: "include",
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
body: {
|
|
37
|
+
userId,
|
|
38
|
+
keyId,
|
|
39
|
+
returnUrl
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const data2 = await response.json().catch(() => ({}));
|
|
45
|
+
throw new Error(data2.message || "Failed to finalize OAuth");
|
|
46
|
+
}
|
|
47
|
+
const data = await response.json();
|
|
48
|
+
onSuccess?.(userId);
|
|
49
|
+
window.location.href = data.returnUrl || returnUrl;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : "OAuth failed";
|
|
52
|
+
setError(message);
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
onError?.(message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finalizeOAuth();
|
|
58
|
+
}, [apiBasePath, onSuccess, onError]);
|
|
59
|
+
if (error) {
|
|
60
|
+
if (errorComponent) {
|
|
61
|
+
return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
|
|
62
|
+
}
|
|
63
|
+
return /* @__PURE__ */ jsxs("div", { style: { padding: "20px", textAlign: "center" }, children: [
|
|
64
|
+
/* @__PURE__ */ jsx("h2", { children: "Authentication Error" }),
|
|
65
|
+
/* @__PURE__ */ jsx("p", { style: { color: "red" }, children: error }),
|
|
66
|
+
/* @__PURE__ */ jsx("button", { onClick: () => window.location.href = "/", children: "Go Home" })
|
|
67
|
+
] });
|
|
68
|
+
}
|
|
69
|
+
if (isLoading) {
|
|
70
|
+
if (loadingComponent) {
|
|
71
|
+
return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent });
|
|
72
|
+
}
|
|
73
|
+
return /* @__PURE__ */ jsx("div", { style: { padding: "20px", textAlign: "center" }, children: /* @__PURE__ */ jsx("p", { children: "Completing authentication..." }) });
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
OAuthCallback
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/components/oauth-callback.tsx"],"sourcesContent":["'use client';\n\n/**\n * OAuthCallback Component\n *\n * OAuth 콜백 페이지용 클라이언트 컴포넌트\n * URL params에서 userId, keyId를 추출하여 oauthFinalize API 호출 후 returnUrl로 리다이렉트\n *\n * @example\n * ```tsx\n * // app/auth/callback/page.tsx\n * export { OAuthCallback as default } from '@spfn/auth/nextjs/client';\n * ```\n */\n\nimport { useEffect, useState } from 'react';\n\nexport interface OAuthCallbackProps\n{\n /**\n * API base path for RPC calls\n * @default '/api/rpc'\n */\n apiBasePath?: string;\n\n /**\n * Custom loading component\n */\n loadingComponent?: React.ReactNode;\n\n /**\n * Custom error component\n */\n errorComponent?: (error: string) => React.ReactNode;\n\n /**\n * Callback after successful OAuth\n */\n onSuccess?: (userId: string) => void;\n\n /**\n * Callback on error\n */\n onError?: (error: string) => void;\n}\n\nexport function OAuthCallback({\n apiBasePath = '/api/rpc',\n loadingComponent,\n errorComponent,\n onSuccess,\n onError,\n}: OAuthCallbackProps)\n{\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() =>\n {\n async function finalizeOAuth()\n {\n try\n {\n const params = new URLSearchParams(window.location.search);\n const userId = params.get('userId');\n const keyId = params.get('keyId');\n const returnUrl = params.get('returnUrl') || '/';\n const errorParam = params.get('error');\n\n // Handle error from backend\n if (errorParam)\n {\n throw new Error(errorParam);\n }\n\n if (!userId || !keyId)\n {\n throw new Error('Missing required parameters');\n }\n\n // Call oauthFinalize API\n const response = await fetch(`${apiBasePath}/oauthFinalize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n credentials: 'include',\n body: JSON.stringify({\n body: {\n userId,\n keyId,\n returnUrl,\n },\n }),\n });\n\n if (!response.ok)\n {\n const data = await response.json().catch(() => ({}));\n throw new Error(data.message || 'Failed to finalize OAuth');\n }\n\n const data = await response.json();\n\n onSuccess?.(userId);\n\n // Redirect to returnUrl\n window.location.href = data.returnUrl || returnUrl;\n }\n catch (err)\n {\n const message = err instanceof Error ? err.message : 'OAuth failed';\n setError(message);\n setIsLoading(false);\n onError?.(message);\n }\n }\n\n finalizeOAuth();\n }, [apiBasePath, onSuccess, onError]);\n\n if (error)\n {\n if (errorComponent)\n {\n return <>{errorComponent(error)}</>;\n }\n\n return (\n <div style={{ padding: '20px', textAlign: 'center' }}>\n <h2>Authentication Error</h2>\n <p style={{ color: 'red' }}>{error}</p>\n <button onClick={() => window.location.href = '/'}>\n Go Home\n </button>\n </div>\n );\n }\n\n if (isLoading)\n {\n if (loadingComponent)\n {\n return <>{loadingComponent}</>;\n }\n\n return (\n <div style={{ padding: '20px', textAlign: 'center' }}>\n <p>Completing authentication...</p>\n </div>\n );\n }\n\n return null;\n}\n\nexport default OAuthCallback;\n"],"mappings":";;;AAeA,SAAS,WAAW,gBAAgB;AA8GjB,wBAIP,YAJO;AA/EZ,SAAS,cAAc;AAAA,EAC1B,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GACA;AACI,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAE/C,YAAU,MACV;AACI,mBAAe,gBACf;AACI,UACA;AACI,cAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACzD,cAAM,SAAS,OAAO,IAAI,QAAQ;AAClC,cAAM,QAAQ,OAAO,IAAI,OAAO;AAChC,cAAM,YAAY,OAAO,IAAI,WAAW,KAAK;AAC7C,cAAM,aAAa,OAAO,IAAI,OAAO;AAGrC,YAAI,YACJ;AACI,gBAAM,IAAI,MAAM,UAAU;AAAA,QAC9B;AAEA,YAAI,CAAC,UAAU,CAAC,OAChB;AACI,gBAAM,IAAI,MAAM,6BAA6B;AAAA,QACjD;AAGA,cAAM,WAAW,MAAM,MAAM,GAAG,WAAW,kBAAkB;AAAA,UACzD,QAAQ;AAAA,UACR,SAAS;AAAA,YACL,gBAAgB;AAAA,UACpB;AAAA,UACA,aAAa;AAAA,UACb,MAAM,KAAK,UAAU;AAAA,YACjB,MAAM;AAAA,cACF;AAAA,cACA;AAAA,cACA;AAAA,YACJ;AAAA,UACJ,CAAC;AAAA,QACL,CAAC;AAED,YAAI,CAAC,SAAS,IACd;AACI,gBAAMA,QAAO,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACnD,gBAAM,IAAI,MAAMA,MAAK,WAAW,0BAA0B;AAAA,QAC9D;AAEA,cAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,oBAAY,MAAM;AAGlB,eAAO,SAAS,OAAO,KAAK,aAAa;AAAA,MAC7C,SACO,KACP;AACI,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,iBAAS,OAAO;AAChB,qBAAa,KAAK;AAClB,kBAAU,OAAO;AAAA,MACrB;AAAA,IACJ;AAEA,kBAAc;AAAA,EAClB,GAAG,CAAC,aAAa,WAAW,OAAO,CAAC;AAEpC,MAAI,OACJ;AACI,QAAI,gBACJ;AACI,aAAO,gCAAG,yBAAe,KAAK,GAAE;AAAA,IACpC;AAEA,WACI,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,WAAW,SAAS,GAC/C;AAAA,0BAAC,QAAG,kCAAoB;AAAA,MACxB,oBAAC,OAAE,OAAO,EAAE,OAAO,MAAM,GAAI,iBAAM;AAAA,MACnC,oBAAC,YAAO,SAAS,MAAM,OAAO,SAAS,OAAO,KAAK,qBAEnD;AAAA,OACJ;AAAA,EAER;AAEA,MAAI,WACJ;AACI,QAAI,kBACJ;AACI,aAAO,gCAAG,4BAAiB;AAAA,IAC/B;AAEA,WACI,oBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,WAAW,SAAS,GAC/C,8BAAC,OAAE,0CAA4B,GACnC;AAAA,EAER;AAEA,SAAO;AACX;","names":["data"]}
|
package/dist/nextjs/server.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { SessionData } from '@spfn/auth/server';
|
|
3
|
+
import { SessionData, KeyAlgorithmType } from '@spfn/auth/server';
|
|
4
4
|
export { SessionData } from '@spfn/auth/server';
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
5
6
|
|
|
6
7
|
interface RequireAuthProps {
|
|
7
8
|
/**
|
|
@@ -161,6 +162,14 @@ declare function hasAnyPermission(requiredPermissions: string[]): Promise<boolea
|
|
|
161
162
|
* Server-side only (uses next/headers)
|
|
162
163
|
*/
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Pending OAuth session data (before user ID is known)
|
|
167
|
+
*/
|
|
168
|
+
interface PendingSessionData {
|
|
169
|
+
privateKey: string;
|
|
170
|
+
keyId: string;
|
|
171
|
+
algorithm: KeyAlgorithmType;
|
|
172
|
+
}
|
|
164
173
|
/**
|
|
165
174
|
* Public session information (excludes sensitive data)
|
|
166
175
|
*/
|
|
@@ -224,5 +233,62 @@ declare function getSession(): Promise<PublicSession | null>;
|
|
|
224
233
|
* Clear session cookie
|
|
225
234
|
*/
|
|
226
235
|
declare function clearSession(): Promise<void>;
|
|
236
|
+
/**
|
|
237
|
+
* Seal pending session data (for OAuth flow)
|
|
238
|
+
*
|
|
239
|
+
* @param data - Pending session data (privateKey, keyId, algorithm)
|
|
240
|
+
* @param ttl - Time to live in seconds (default: 10 minutes)
|
|
241
|
+
*/
|
|
242
|
+
declare function sealPendingSession(data: PendingSessionData, ttl?: number): Promise<string>;
|
|
243
|
+
/**
|
|
244
|
+
* Unseal pending session data
|
|
245
|
+
*
|
|
246
|
+
* @param jwt - Encrypted pending session token
|
|
247
|
+
*/
|
|
248
|
+
declare function unsealPendingSession(jwt: string): Promise<PendingSessionData>;
|
|
249
|
+
/**
|
|
250
|
+
* Get pending session from cookie
|
|
251
|
+
*/
|
|
252
|
+
declare function getPendingSession(): Promise<PendingSessionData | null>;
|
|
253
|
+
/**
|
|
254
|
+
* Clear pending session cookie
|
|
255
|
+
*/
|
|
256
|
+
declare function clearPendingSession(): Promise<void>;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* OAuth Handlers for Next.js
|
|
260
|
+
*
|
|
261
|
+
* Helper functions to create OAuth callback route handlers
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
interface OAuthCallbackOptions {
|
|
265
|
+
/**
|
|
266
|
+
* Default redirect URL if returnUrl is not provided
|
|
267
|
+
* @default '/'
|
|
268
|
+
*/
|
|
269
|
+
defaultRedirectUrl?: string;
|
|
270
|
+
/**
|
|
271
|
+
* Error redirect URL
|
|
272
|
+
* @default '/auth/error'
|
|
273
|
+
*/
|
|
274
|
+
errorRedirectUrl?: string;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Create OAuth callback handler for Next.js API Route
|
|
278
|
+
*
|
|
279
|
+
* Handles the final step of OAuth flow:
|
|
280
|
+
* 1. Gets userId, keyId from query params (set by backend)
|
|
281
|
+
* 2. Gets privateKey from pending session cookie
|
|
282
|
+
* 3. Creates full session and saves to cookie
|
|
283
|
+
* 4. Redirects to returnUrl
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* // /api/auth/callback/route.ts
|
|
288
|
+
* import { createOAuthCallbackHandler } from '@spfn/auth/nextjs/server';
|
|
289
|
+
* export const GET = createOAuthCallbackHandler();
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
declare function createOAuthCallbackHandler(options?: OAuthCallbackOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
227
293
|
|
|
228
|
-
export { type PublicSession, RequireAuth, type RequireAuthProps, RequirePermission, type RequirePermissionProps, RequireRole, type RequireRoleProps, type SaveSessionOptions, clearSession, getSession, getUserPermissions, getUserRole, hasAnyPermission, hasAnyRole, saveSession };
|
|
294
|
+
export { type OAuthCallbackOptions, type PendingSessionData, type PublicSession, RequireAuth, type RequireAuthProps, RequirePermission, type RequirePermissionProps, RequireRole, type RequireRoleProps, type SaveSessionOptions, clearPendingSession, clearSession, createOAuthCallbackHandler, getPendingSession, getSession, getUserPermissions, getUserRole, hasAnyPermission, hasAnyRole, saveSession, sealPendingSession, unsealPendingSession };
|
package/dist/nextjs/server.js
CHANGED
|
@@ -5,8 +5,16 @@ import "server-only";
|
|
|
5
5
|
import { redirect } from "next/navigation";
|
|
6
6
|
|
|
7
7
|
// src/nextjs/session-helpers.ts
|
|
8
|
+
import * as jose from "jose";
|
|
8
9
|
import { cookies } from "next/headers.js";
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
sealSession,
|
|
12
|
+
unsealSession,
|
|
13
|
+
COOKIE_NAMES,
|
|
14
|
+
getSessionTtl,
|
|
15
|
+
parseDuration
|
|
16
|
+
} from "@spfn/auth/server";
|
|
17
|
+
import { env } from "@spfn/auth/config";
|
|
10
18
|
import { logger } from "@spfn/core/logger";
|
|
11
19
|
async function saveSession(data, options) {
|
|
12
20
|
let maxAge;
|
|
@@ -49,6 +57,44 @@ async function clearSession() {
|
|
|
49
57
|
cookieStore.delete(COOKIE_NAMES.SESSION);
|
|
50
58
|
cookieStore.delete(COOKIE_NAMES.SESSION_KEY_ID);
|
|
51
59
|
}
|
|
60
|
+
async function getPendingSessionKey() {
|
|
61
|
+
const secret = env.SPFN_AUTH_SESSION_SECRET;
|
|
62
|
+
const encoder = new TextEncoder();
|
|
63
|
+
const data = encoder.encode(`oauth-pending:${secret}`);
|
|
64
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
65
|
+
return new Uint8Array(hashBuffer);
|
|
66
|
+
}
|
|
67
|
+
async function sealPendingSession(data, ttl = 600) {
|
|
68
|
+
const key = await getPendingSessionKey();
|
|
69
|
+
return await new jose.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-oauth").encrypt(key);
|
|
70
|
+
}
|
|
71
|
+
async function unsealPendingSession(jwt) {
|
|
72
|
+
const key = await getPendingSessionKey();
|
|
73
|
+
const { payload } = await jose.jwtDecrypt(jwt, key, {
|
|
74
|
+
issuer: "spfn-auth",
|
|
75
|
+
audience: "spfn-oauth"
|
|
76
|
+
});
|
|
77
|
+
return payload.data;
|
|
78
|
+
}
|
|
79
|
+
async function getPendingSession() {
|
|
80
|
+
const cookieStore = await cookies();
|
|
81
|
+
const pendingCookie = cookieStore.get(COOKIE_NAMES.OAUTH_PENDING);
|
|
82
|
+
if (!pendingCookie) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return await unsealPendingSession(pendingCookie.value);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.debug("Pending session validation failed", {
|
|
89
|
+
error: error instanceof Error ? error.message : String(error)
|
|
90
|
+
});
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function clearPendingSession() {
|
|
95
|
+
const cookieStore = await cookies();
|
|
96
|
+
cookieStore.delete(COOKIE_NAMES.OAUTH_PENDING);
|
|
97
|
+
}
|
|
52
98
|
|
|
53
99
|
// src/nextjs/guards/require-auth.tsx
|
|
54
100
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
@@ -162,16 +208,93 @@ async function RequirePermission({
|
|
|
162
208
|
}
|
|
163
209
|
return /* @__PURE__ */ jsx3(Fragment3, { children });
|
|
164
210
|
}
|
|
211
|
+
|
|
212
|
+
// src/nextjs/oauth-handlers.ts
|
|
213
|
+
import { NextResponse } from "next/server";
|
|
214
|
+
import { cookies as cookies2 } from "next/headers.js";
|
|
215
|
+
import { sealSession as sealSession2, COOKIE_NAMES as COOKIE_NAMES2, getSessionTtl as getSessionTtl2 } from "@spfn/auth/server";
|
|
216
|
+
import { env as env2 } from "@spfn/core/config";
|
|
217
|
+
import { logger as logger2 } from "@spfn/core/logger";
|
|
218
|
+
function createOAuthCallbackHandler(options) {
|
|
219
|
+
const defaultRedirect = options?.defaultRedirectUrl || "/";
|
|
220
|
+
const errorRedirect = options?.errorRedirectUrl || "/auth/error";
|
|
221
|
+
return async (request) => {
|
|
222
|
+
const searchParams = request.nextUrl.searchParams;
|
|
223
|
+
const userId = searchParams.get("userId");
|
|
224
|
+
const keyId = searchParams.get("keyId");
|
|
225
|
+
const returnUrl = searchParams.get("returnUrl") || defaultRedirect;
|
|
226
|
+
const error = searchParams.get("error");
|
|
227
|
+
if (error) {
|
|
228
|
+
const errorUrl = new URL(errorRedirect, request.url);
|
|
229
|
+
errorUrl.searchParams.set("error", error);
|
|
230
|
+
return NextResponse.redirect(errorUrl);
|
|
231
|
+
}
|
|
232
|
+
if (!userId || !keyId) {
|
|
233
|
+
logger2.error("OAuth callback missing required params", { userId: !!userId, keyId: !!keyId });
|
|
234
|
+
const errorUrl = new URL(errorRedirect, request.url);
|
|
235
|
+
errorUrl.searchParams.set("error", "Missing required parameters");
|
|
236
|
+
return NextResponse.redirect(errorUrl);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const cookieStore = await cookies2();
|
|
240
|
+
const pendingCookie = cookieStore.get(COOKIE_NAMES2.OAUTH_PENDING);
|
|
241
|
+
if (!pendingCookie) {
|
|
242
|
+
throw new Error("OAuth session expired. Please try again.");
|
|
243
|
+
}
|
|
244
|
+
const pendingSession = await unsealPendingSession(pendingCookie.value);
|
|
245
|
+
if (pendingSession.keyId !== keyId) {
|
|
246
|
+
throw new Error("Session mismatch. Please try again.");
|
|
247
|
+
}
|
|
248
|
+
const ttl = getSessionTtl2();
|
|
249
|
+
const sessionToken = await sealSession2({
|
|
250
|
+
userId,
|
|
251
|
+
privateKey: pendingSession.privateKey,
|
|
252
|
+
keyId: pendingSession.keyId,
|
|
253
|
+
algorithm: pendingSession.algorithm
|
|
254
|
+
}, ttl);
|
|
255
|
+
const redirectUrl = new URL(returnUrl, request.url);
|
|
256
|
+
const response = NextResponse.redirect(redirectUrl);
|
|
257
|
+
response.cookies.set(COOKIE_NAMES2.SESSION, sessionToken, {
|
|
258
|
+
httpOnly: true,
|
|
259
|
+
secure: env2.NODE_ENV === "production",
|
|
260
|
+
sameSite: "strict",
|
|
261
|
+
maxAge: ttl,
|
|
262
|
+
path: "/"
|
|
263
|
+
});
|
|
264
|
+
response.cookies.set(COOKIE_NAMES2.SESSION_KEY_ID, keyId, {
|
|
265
|
+
httpOnly: true,
|
|
266
|
+
secure: env2.NODE_ENV === "production",
|
|
267
|
+
sameSite: "strict",
|
|
268
|
+
maxAge: ttl,
|
|
269
|
+
path: "/"
|
|
270
|
+
});
|
|
271
|
+
response.cookies.delete(COOKIE_NAMES2.OAUTH_PENDING);
|
|
272
|
+
logger2.debug("OAuth callback completed", { userId, keyId });
|
|
273
|
+
return response;
|
|
274
|
+
} catch (error2) {
|
|
275
|
+
const err = error2;
|
|
276
|
+
logger2.error("OAuth callback failed", { error: err.message });
|
|
277
|
+
const errorUrl = new URL(errorRedirect, request.url);
|
|
278
|
+
errorUrl.searchParams.set("error", err.message);
|
|
279
|
+
return NextResponse.redirect(errorUrl);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
165
283
|
export {
|
|
166
284
|
RequireAuth,
|
|
167
285
|
RequirePermission,
|
|
168
286
|
RequireRole,
|
|
287
|
+
clearPendingSession,
|
|
169
288
|
clearSession,
|
|
289
|
+
createOAuthCallbackHandler,
|
|
290
|
+
getPendingSession,
|
|
170
291
|
getSession,
|
|
171
292
|
getUserPermissions,
|
|
172
293
|
getUserRole,
|
|
173
294
|
hasAnyPermission,
|
|
174
295
|
hasAnyRole,
|
|
175
|
-
saveSession
|
|
296
|
+
saveSession,
|
|
297
|
+
sealPendingSession,
|
|
298
|
+
unsealPendingSession
|
|
176
299
|
};
|
|
177
300
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nextjs/server.ts","../../src/nextjs/guards/require-auth.tsx","../../src/nextjs/session-helpers.ts","../../src/nextjs/guards/require-role.tsx","../../src/nextjs/guards/auth-utils.ts","../../src/nextjs/guards/require-permission.tsx"],"sourcesContent":["import \"server-only\";\n\nexport { RequireAuth } from './guards/require-auth';\nexport type { RequireAuthProps } from './guards/require-auth';\n\nexport { RequireRole } from './guards/require-role';\nexport type { RequireRoleProps } from './guards/require-role';\n\nexport { RequirePermission } from './guards/require-permission';\nexport type { RequirePermissionProps } from './guards/require-permission';\n\nexport { getUserRole, getUserPermissions, hasAnyRole, hasAnyPermission } from './guards/auth-utils';\n\n// Session helpers\nexport {\n saveSession,\n getSession,\n clearSession,\n type SessionData,\n type PublicSession,\n type SaveSessionOptions\n} from './session-helpers';","/**\n * RequireAuth Guard Component\n *\n * Requires user to be authenticated\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport type { ReactNode } from 'react';\n\nexport interface RequireAuthProps\n{\n /**\n * Children to render if authenticated\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if not authenticated\n * @default '/login'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Authentication Guard\n *\n * Ensures user is logged in before rendering children\n *\n * @example\n * ```tsx\n * <RequireAuth redirectTo=\"/login\">\n * <DashboardContent />\n * </RequireAuth>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireAuth fallback={<LoginPrompt />}>\n * <PrivateContent />\n * </RequireAuth>\n * ```\n */\nexport async function RequireAuth({\n children,\n redirectTo = '/auth/login',\n fallback,\n}: RequireAuthProps)\n{\n const session = await getSession();\n\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Session helpers for Next.js\n *\n * Server-side only (uses next/headers)\n */\n\nimport { cookies } from 'next/headers.js';\nimport { sealSession, unsealSession, COOKIE_NAMES, getSessionTtl, parseDuration, type SessionData } from '@spfn/auth/server';\nimport { logger } from '@spfn/core/logger';\n\nexport type { SessionData };\n\n/**\n * Public session information (excludes sensitive data)\n */\nexport interface PublicSession\n{\n /** User ID */\n userId: string;\n}\n\n/**\n * Options for saveSession\n */\nexport interface SaveSessionOptions\n{\n /**\n * Session TTL (time to live)\n *\n * Supports:\n * - Number: seconds (e.g., 2592000)\n * - String: duration format ('30d', '12h', '45m', '3600s')\n *\n * If not provided, uses global configuration:\n * 1. Global config (configureAuth)\n * 2. Environment variable (SPFN_AUTH_SESSION_TTL)\n * 3. Default (7d)\n */\n maxAge?: number | string;\n\n /**\n * Remember me option\n *\n * When true, uses extended session duration (if configured)\n */\n remember?: boolean;\n}\n\n/**\n * Save session to HttpOnly cookie\n *\n * @param data - Session data to save\n * @param options - Session options (maxAge, remember)\n *\n * @example\n * ```typescript\n * // Use global configuration\n * await saveSession(sessionData);\n *\n * // Custom TTL with duration string\n * await saveSession(sessionData, { maxAge: '30d' });\n *\n * // Custom TTL in seconds\n * await saveSession(sessionData, { maxAge: 2592000 });\n *\n * // Remember me\n * await saveSession(sessionData, { remember: true });\n * ```\n */\nexport async function saveSession(\n data: SessionData,\n options?: SaveSessionOptions\n): Promise<void>\n{\n // Calculate maxAge\n let maxAge: number;\n\n if (options?.maxAge !== undefined)\n {\n // Custom maxAge provided\n maxAge = typeof options.maxAge === 'number'\n ? options.maxAge\n : parseDuration(options.maxAge);\n }\n else\n {\n // Use getSessionTtl for consistent configuration\n maxAge = getSessionTtl();\n }\n\n const token = await sealSession(data, maxAge);\n const cookieStore = await cookies();\n\n cookieStore.set(COOKIE_NAMES.SESSION, token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n path: '/',\n maxAge\n });\n}\n\n/**\n * Get session from HttpOnly cookie\n *\n * Returns public session info only (excludes privateKey, algorithm, keyId)\n */\nexport async function getSession(): Promise<PublicSession | null>\n{\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n return null;\n }\n\n try\n {\n logger.debug('Validating session cookie', { cookie: sessionCookie.value });\n const session = await unsealSession(sessionCookie.value);\n // Return only public information\n return {\n userId: session.userId,\n };\n }\n catch (error)\n {\n // Session expired or invalid\n // Note: Cannot delete cookies in Server Components (read-only)\n // Use validateSessionMiddleware() in Next.js middleware for automatic cleanup\n logger.debug('Session validation failed', {\n error: error instanceof Error ? error.message : String(error)\n });\n\n return null;\n }\n}\n\n/**\n * Clear session cookie\n */\nexport async function clearSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.SESSION);\n cookieStore.delete(COOKIE_NAMES.SESSION_KEY_ID);\n}\n","/**\n * RequireRole Guard Component\n *\n * Requires user to have at least one of the specified roles\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyRole } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequireRoleProps\n{\n /**\n * Required role(s) - user must have at least one\n */\n roles: string | string[];\n\n /**\n * Children to render if user has required role\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have role\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Role Guard\n *\n * Ensures user has at least one of the specified roles\n *\n * @example Single role\n * ```tsx\n * <RequireRole roles=\"admin\">\n * <AdminPanel />\n * </RequireRole>\n * ```\n *\n * @example Multiple roles (OR condition)\n * ```tsx\n * <RequireRole roles={['admin', 'manager']}>\n * <ManagementDashboard />\n * </RequireRole>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireRole roles=\"admin\" fallback={<AccessDenied />}>\n * <AdminContent />\n * </RequireRole>\n * ```\n */\nexport async function RequireRole({\n roles,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequireRoleProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredRoles = Array.isArray(roles) ? roles : [roles];\n\n // Check if user has any of the required roles\n const hasRole = await hasAnyRole(requiredRoles);\n\n if (!hasRole)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Server-side auth utilities for guards\n *\n * Uses authApi to check permissions in real-time\n */\n\nimport { authApi } from '@spfn/auth';\nimport { authLogger } from '@spfn/auth/server';\n\n/**\n * Get current auth session with roles and permissions via API\n */\nasync function getAuthSessionData()\n{\n try\n {\n const session = await authApi.getAuthSession.call();\n authLogger.middleware.debug('Auth session retrieved', { name: session.role?.name });\n\n return session;\n }\n catch (error)\n {\n authLogger.middleware.error('Failed to get auth session', { error });\n return null;\n }\n}\n\n/**\n * Get user role\n */\nexport async function getUserRole(): Promise<string | null>\n{\n const session = await getAuthSessionData();\n return session?.role?.name || null;\n}\n\n/**\n * Get user permissions\n */\nexport async function getUserPermissions(): Promise<string[]>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return [];\n }\n\n return session.permissions?.map((p: any) => p.name) || [];\n}\n\n/**\n * Check if user has any of the specified roles\n */\nexport async function hasAnyRole(requiredRoles: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n if (!session)\n {\n return false;\n }\n\n return requiredRoles.includes(session.role?.name);\n}\n\n/**\n * Check if user has any of the specified permissions\n */\nexport async function hasAnyPermission(requiredPermissions: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return false;\n }\n\n const userPermissionNames = session.permissions?.map((p: any) => p.name) || [];\n return requiredPermissions.some(permission => userPermissionNames.includes(permission));\n}\n","/**\n * RequirePermission Guard Component\n *\n * Requires user to have at least one of the specified permissions\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyPermission } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequirePermissionProps\n{\n /**\n * Required permission(s) - user must have at least one\n */\n permissions: string | string[];\n\n /**\n * Children to render if user has required permission\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have permission\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Permission Guard\n *\n * Ensures user has at least one of the specified permissions\n *\n * @example Single permission\n * ```tsx\n * <RequirePermission permissions=\"user:delete\">\n * <DeleteUserButton />\n * </RequirePermission>\n * ```\n *\n * @example Multiple permissions (OR condition)\n * ```tsx\n * <RequirePermission permissions={['user:delete', 'user:update']}>\n * <UserManagement />\n * </RequirePermission>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequirePermission permissions=\"project:create\" fallback={<UpgradePrompt />}>\n * <CreateProject />\n * </RequirePermission>\n * ```\n */\nexport async function RequirePermission({\n permissions,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequirePermissionProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];\n\n // Check if user has any of the required permissions\n const hasPermission = await hasAnyPermission(requiredPermissions);\n\n if (!hasPermission)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}"],"mappings":"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","names":["redirect","Fragment","jsx","redirect","redirect","Fragment","jsx","redirect"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/server.ts","../../src/nextjs/guards/require-auth.tsx","../../src/nextjs/session-helpers.ts","../../src/nextjs/guards/require-role.tsx","../../src/nextjs/guards/auth-utils.ts","../../src/nextjs/guards/require-permission.tsx","../../src/nextjs/oauth-handlers.ts"],"sourcesContent":["import \"server-only\";\n\nexport { RequireAuth } from './guards/require-auth';\nexport type { RequireAuthProps } from './guards/require-auth';\n\nexport { RequireRole } from './guards/require-role';\nexport type { RequireRoleProps } from './guards/require-role';\n\nexport { RequirePermission } from './guards/require-permission';\nexport type { RequirePermissionProps } from './guards/require-permission';\n\nexport { getUserRole, getUserPermissions, hasAnyRole, hasAnyPermission } from './guards/auth-utils';\n\n// Session helpers\nexport {\n saveSession,\n getSession,\n clearSession,\n // Pending session (OAuth)\n sealPendingSession,\n unsealPendingSession,\n getPendingSession,\n clearPendingSession,\n type SessionData,\n type PublicSession,\n type SaveSessionOptions,\n type PendingSessionData,\n} from './session-helpers';\n\n// OAuth handlers\nexport {\n createOAuthCallbackHandler,\n type OAuthCallbackOptions,\n} from './oauth-handlers';","/**\n * RequireAuth Guard Component\n *\n * Requires user to be authenticated\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport type { ReactNode } from 'react';\n\nexport interface RequireAuthProps\n{\n /**\n * Children to render if authenticated\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if not authenticated\n * @default '/login'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Authentication Guard\n *\n * Ensures user is logged in before rendering children\n *\n * @example\n * ```tsx\n * <RequireAuth redirectTo=\"/login\">\n * <DashboardContent />\n * </RequireAuth>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireAuth fallback={<LoginPrompt />}>\n * <PrivateContent />\n * </RequireAuth>\n * ```\n */\nexport async function RequireAuth({\n children,\n redirectTo = '/auth/login',\n fallback,\n}: RequireAuthProps)\n{\n const session = await getSession();\n\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Session helpers for Next.js\n *\n * Server-side only (uses next/headers)\n */\n\nimport * as jose from 'jose';\nimport { cookies } from 'next/headers.js';\nimport {\n sealSession,\n unsealSession,\n COOKIE_NAMES,\n getSessionTtl,\n parseDuration,\n type SessionData,\n type KeyAlgorithmType,\n} from '@spfn/auth/server';\nimport { env } from '@spfn/auth/config';\nimport { logger } from '@spfn/core/logger';\n\nexport type { SessionData };\n\n/**\n * Pending OAuth session data (before user ID is known)\n */\nexport interface PendingSessionData\n{\n privateKey: string;\n keyId: string;\n algorithm: KeyAlgorithmType;\n}\n\n/**\n * Public session information (excludes sensitive data)\n */\nexport interface PublicSession\n{\n /** User ID */\n userId: string;\n}\n\n/**\n * Options for saveSession\n */\nexport interface SaveSessionOptions\n{\n /**\n * Session TTL (time to live)\n *\n * Supports:\n * - Number: seconds (e.g., 2592000)\n * - String: duration format ('30d', '12h', '45m', '3600s')\n *\n * If not provided, uses global configuration:\n * 1. Global config (configureAuth)\n * 2. Environment variable (SPFN_AUTH_SESSION_TTL)\n * 3. Default (7d)\n */\n maxAge?: number | string;\n\n /**\n * Remember me option\n *\n * When true, uses extended session duration (if configured)\n */\n remember?: boolean;\n}\n\n/**\n * Save session to HttpOnly cookie\n *\n * @param data - Session data to save\n * @param options - Session options (maxAge, remember)\n *\n * @example\n * ```typescript\n * // Use global configuration\n * await saveSession(sessionData);\n *\n * // Custom TTL with duration string\n * await saveSession(sessionData, { maxAge: '30d' });\n *\n * // Custom TTL in seconds\n * await saveSession(sessionData, { maxAge: 2592000 });\n *\n * // Remember me\n * await saveSession(sessionData, { remember: true });\n * ```\n */\nexport async function saveSession(\n data: SessionData,\n options?: SaveSessionOptions\n): Promise<void>\n{\n // Calculate maxAge\n let maxAge: number;\n\n if (options?.maxAge !== undefined)\n {\n // Custom maxAge provided\n maxAge = typeof options.maxAge === 'number'\n ? options.maxAge\n : parseDuration(options.maxAge);\n }\n else\n {\n // Use getSessionTtl for consistent configuration\n maxAge = getSessionTtl();\n }\n\n const token = await sealSession(data, maxAge);\n const cookieStore = await cookies();\n\n cookieStore.set(COOKIE_NAMES.SESSION, token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n path: '/',\n maxAge\n });\n}\n\n/**\n * Get session from HttpOnly cookie\n *\n * Returns public session info only (excludes privateKey, algorithm, keyId)\n */\nexport async function getSession(): Promise<PublicSession | null>\n{\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n return null;\n }\n\n try\n {\n logger.debug('Validating session cookie', { cookie: sessionCookie.value });\n const session = await unsealSession(sessionCookie.value);\n // Return only public information\n return {\n userId: session.userId,\n };\n }\n catch (error)\n {\n // Session expired or invalid\n // Note: Cannot delete cookies in Server Components (read-only)\n // Use validateSessionMiddleware() in Next.js middleware for automatic cleanup\n logger.debug('Session validation failed', {\n error: error instanceof Error ? error.message : String(error)\n });\n\n return null;\n }\n}\n\n/**\n * Clear session cookie\n */\nexport async function clearSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.SESSION);\n cookieStore.delete(COOKIE_NAMES.SESSION_KEY_ID);\n}\n\n// ============================================================================\n// Pending OAuth Session (for OAuth flow)\n// ============================================================================\n\n/**\n * Get encryption key for pending session\n */\nasync function getPendingSessionKey(): Promise<Uint8Array>\n{\n const secret = env.SPFN_AUTH_SESSION_SECRET;\n const encoder = new TextEncoder();\n const data = encoder.encode(`oauth-pending:${secret}`);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n return new Uint8Array(hashBuffer);\n}\n\n/**\n * Seal pending session data (for OAuth flow)\n *\n * @param data - Pending session data (privateKey, keyId, algorithm)\n * @param ttl - Time to live in seconds (default: 10 minutes)\n */\nexport async function sealPendingSession(\n data: PendingSessionData,\n ttl: number = 600\n): Promise<string>\n{\n const key = await getPendingSessionKey();\n\n return await new jose.EncryptJWT({ data })\n .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })\n .setIssuedAt()\n .setExpirationTime(`${ttl}s`)\n .setIssuer('spfn-auth')\n .setAudience('spfn-oauth')\n .encrypt(key);\n}\n\n/**\n * Unseal pending session data\n *\n * @param jwt - Encrypted pending session token\n */\nexport async function unsealPendingSession(jwt: string): Promise<PendingSessionData>\n{\n const key = await getPendingSessionKey();\n\n const { payload } = await jose.jwtDecrypt(jwt, key, {\n issuer: 'spfn-auth',\n audience: 'spfn-oauth',\n });\n\n return payload.data as PendingSessionData;\n}\n\n/**\n * Get pending session from cookie\n */\nexport async function getPendingSession(): Promise<PendingSessionData | null>\n{\n const cookieStore = await cookies();\n const pendingCookie = cookieStore.get(COOKIE_NAMES.OAUTH_PENDING);\n\n if (!pendingCookie)\n {\n return null;\n }\n\n try\n {\n return await unsealPendingSession(pendingCookie.value);\n }\n catch (error)\n {\n logger.debug('Pending session validation failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n return null;\n }\n}\n\n/**\n * Clear pending session cookie\n */\nexport async function clearPendingSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.OAUTH_PENDING);\n}\n","/**\n * RequireRole Guard Component\n *\n * Requires user to have at least one of the specified roles\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyRole } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequireRoleProps\n{\n /**\n * Required role(s) - user must have at least one\n */\n roles: string | string[];\n\n /**\n * Children to render if user has required role\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have role\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Role Guard\n *\n * Ensures user has at least one of the specified roles\n *\n * @example Single role\n * ```tsx\n * <RequireRole roles=\"admin\">\n * <AdminPanel />\n * </RequireRole>\n * ```\n *\n * @example Multiple roles (OR condition)\n * ```tsx\n * <RequireRole roles={['admin', 'manager']}>\n * <ManagementDashboard />\n * </RequireRole>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireRole roles=\"admin\" fallback={<AccessDenied />}>\n * <AdminContent />\n * </RequireRole>\n * ```\n */\nexport async function RequireRole({\n roles,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequireRoleProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredRoles = Array.isArray(roles) ? roles : [roles];\n\n // Check if user has any of the required roles\n const hasRole = await hasAnyRole(requiredRoles);\n\n if (!hasRole)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Server-side auth utilities for guards\n *\n * Uses authApi to check permissions in real-time\n */\n\nimport { authApi } from '@spfn/auth';\nimport { authLogger } from '@spfn/auth/server';\n\n/**\n * Get current auth session with roles and permissions via API\n */\nasync function getAuthSessionData()\n{\n try\n {\n const session = await authApi.getAuthSession.call();\n authLogger.middleware.debug('Auth session retrieved', { name: session.role?.name });\n\n return session;\n }\n catch (error)\n {\n authLogger.middleware.error('Failed to get auth session', { error });\n return null;\n }\n}\n\n/**\n * Get user role\n */\nexport async function getUserRole(): Promise<string | null>\n{\n const session = await getAuthSessionData();\n return session?.role?.name || null;\n}\n\n/**\n * Get user permissions\n */\nexport async function getUserPermissions(): Promise<string[]>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return [];\n }\n\n return session.permissions?.map((p: any) => p.name) || [];\n}\n\n/**\n * Check if user has any of the specified roles\n */\nexport async function hasAnyRole(requiredRoles: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n if (!session)\n {\n return false;\n }\n\n return requiredRoles.includes(session.role?.name);\n}\n\n/**\n * Check if user has any of the specified permissions\n */\nexport async function hasAnyPermission(requiredPermissions: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return false;\n }\n\n const userPermissionNames = session.permissions?.map((p: any) => p.name) || [];\n return requiredPermissions.some(permission => userPermissionNames.includes(permission));\n}\n","/**\n * RequirePermission Guard Component\n *\n * Requires user to have at least one of the specified permissions\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyPermission } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequirePermissionProps\n{\n /**\n * Required permission(s) - user must have at least one\n */\n permissions: string | string[];\n\n /**\n * Children to render if user has required permission\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have permission\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Permission Guard\n *\n * Ensures user has at least one of the specified permissions\n *\n * @example Single permission\n * ```tsx\n * <RequirePermission permissions=\"user:delete\">\n * <DeleteUserButton />\n * </RequirePermission>\n * ```\n *\n * @example Multiple permissions (OR condition)\n * ```tsx\n * <RequirePermission permissions={['user:delete', 'user:update']}>\n * <UserManagement />\n * </RequirePermission>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequirePermission permissions=\"project:create\" fallback={<UpgradePrompt />}>\n * <CreateProject />\n * </RequirePermission>\n * ```\n */\nexport async function RequirePermission({\n permissions,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequirePermissionProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];\n\n // Check if user has any of the required permissions\n const hasPermission = await hasAnyPermission(requiredPermissions);\n\n if (!hasPermission)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * OAuth Handlers for Next.js\n *\n * Helper functions to create OAuth callback route handlers\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { cookies } from 'next/headers.js';\nimport { sealSession, COOKIE_NAMES, getSessionTtl } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\nimport { logger } from '@spfn/core/logger';\nimport { unsealPendingSession } from './session-helpers';\n\nexport interface OAuthCallbackOptions\n{\n /**\n * Default redirect URL if returnUrl is not provided\n * @default '/'\n */\n defaultRedirectUrl?: string;\n\n /**\n * Error redirect URL\n * @default '/auth/error'\n */\n errorRedirectUrl?: string;\n}\n\n/**\n * Create OAuth callback handler for Next.js API Route\n *\n * Handles the final step of OAuth flow:\n * 1. Gets userId, keyId from query params (set by backend)\n * 2. Gets privateKey from pending session cookie\n * 3. Creates full session and saves to cookie\n * 4. Redirects to returnUrl\n *\n * @example\n * ```typescript\n * // /api/auth/callback/route.ts\n * import { createOAuthCallbackHandler } from '@spfn/auth/nextjs/server';\n * export const GET = createOAuthCallbackHandler();\n * ```\n */\nexport function createOAuthCallbackHandler(options?: OAuthCallbackOptions)\n{\n const defaultRedirect = options?.defaultRedirectUrl || '/';\n const errorRedirect = options?.errorRedirectUrl || '/auth/error';\n\n return async (request: NextRequest): Promise<NextResponse> =>\n {\n const searchParams = request.nextUrl.searchParams;\n const userId = searchParams.get('userId');\n const keyId = searchParams.get('keyId');\n const returnUrl = searchParams.get('returnUrl') || defaultRedirect;\n const error = searchParams.get('error');\n\n // Handle error from backend\n if (error)\n {\n const errorUrl = new URL(errorRedirect, request.url);\n errorUrl.searchParams.set('error', error);\n return NextResponse.redirect(errorUrl);\n }\n\n // Validate required params\n if (!userId || !keyId)\n {\n logger.error('OAuth callback missing required params', { userId: !!userId, keyId: !!keyId });\n const errorUrl = new URL(errorRedirect, request.url);\n errorUrl.searchParams.set('error', 'Missing required parameters');\n return NextResponse.redirect(errorUrl);\n }\n\n try\n {\n // Get pending session from cookie\n const cookieStore = await cookies();\n const pendingCookie = cookieStore.get(COOKIE_NAMES.OAUTH_PENDING);\n\n if (!pendingCookie)\n {\n throw new Error('OAuth session expired. Please try again.');\n }\n\n const pendingSession = await unsealPendingSession(pendingCookie.value);\n\n // Verify keyId matches\n if (pendingSession.keyId !== keyId)\n {\n throw new Error('Session mismatch. Please try again.');\n }\n\n // Create full session\n const ttl = getSessionTtl();\n const sessionToken = await sealSession({\n userId,\n privateKey: pendingSession.privateKey,\n keyId: pendingSession.keyId,\n algorithm: pendingSession.algorithm,\n }, ttl);\n\n // Build redirect response\n const redirectUrl = new URL(returnUrl, request.url);\n const response = NextResponse.redirect(redirectUrl);\n\n // Set session cookie\n response.cookies.set(COOKIE_NAMES.SESSION, sessionToken, {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n });\n\n // Set keyId cookie\n response.cookies.set(COOKIE_NAMES.SESSION_KEY_ID, keyId, {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n });\n\n // Clear pending session cookie\n response.cookies.delete(COOKIE_NAMES.OAUTH_PENDING);\n\n logger.debug('OAuth callback completed', { userId, keyId });\n\n return response;\n }\n catch (error)\n {\n const err = error as Error;\n logger.error('OAuth callback failed', { error: err.message });\n\n const errorUrl = new URL(errorRedirect, request.url);\n errorUrl.searchParams.set('error', err.message);\n return NextResponse.redirect(errorUrl);\n }\n };\n}\n"],"mappings":"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","names":["redirect","Fragment","jsx","redirect","redirect","Fragment","jsx","redirect","cookies","sealSession","COOKIE_NAMES","getSessionTtl","env","logger","logger","cookies","COOKIE_NAMES","getSessionTtl","sealSession","env","error"]}
|