@cas-system/nextjs-cas-client 1.0.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 +148 -0
- package/dist/CasProvider.d.ts +63 -0
- package/dist/CasProvider.d.ts.map +1 -0
- package/dist/CasProvider.js +150 -0
- package/dist/CasProvider.js.map +1 -0
- package/dist/components/CasLoginButton.d.ts +36 -0
- package/dist/components/CasLoginButton.d.ts.map +1 -0
- package/dist/components/CasLoginButton.js +23 -0
- package/dist/components/CasLoginButton.js.map +1 -0
- package/dist/components/CasProtectedRoute.d.ts +51 -0
- package/dist/components/CasProtectedRoute.d.ts.map +1 -0
- package/dist/components/CasProtectedRoute.js +58 -0
- package/dist/components/CasProtectedRoute.js.map +1 -0
- package/dist/handlers/callback.d.ts +39 -0
- package/dist/handlers/callback.d.ts.map +1 -0
- package/dist/handlers/callback.js +65 -0
- package/dist/handlers/callback.js.map +1 -0
- package/dist/handlers/index.d.ts +8 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +8 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/logout.d.ts +36 -0
- package/dist/handlers/logout.d.ts.map +1 -0
- package/dist/handlers/logout.js +61 -0
- package/dist/handlers/logout.js.map +1 -0
- package/dist/handlers/user.d.ts +27 -0
- package/dist/handlers/user.d.ts.map +1 -0
- package/dist/handlers/user.js +44 -0
- package/dist/handlers/user.js.map +1 -0
- package/dist/hooks/useCasAuth.d.ts +32 -0
- package/dist/hooks/useCasAuth.d.ts.map +1 -0
- package/dist/hooks/useCasAuth.js +14 -0
- package/dist/hooks/useCasAuth.js.map +1 -0
- package/dist/hooks/useCasUser.d.ts +33 -0
- package/dist/hooks/useCasUser.d.ts.map +1 -0
- package/dist/hooks/useCasUser.js +45 -0
- package/dist/hooks/useCasUser.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +40 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +111 -0
- package/dist/middleware.js.map +1 -0
- package/dist/server/auth.d.ts +111 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +215 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/cas-client.d.ts +93 -0
- package/dist/server/cas-client.d.ts.map +1 -0
- package/dist/server/cas-client.js +222 -0
- package/dist/server/cas-client.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +11 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/server/auth
|
|
3
|
+
* @description Server-side session helpers for reading / writing CAS session
|
|
4
|
+
* data to Next.js cookies.
|
|
5
|
+
*
|
|
6
|
+
* The session is stored as a base-64 encoded JSON blob (signed with HMAC)
|
|
7
|
+
* inside an `HttpOnly`, `Secure`, `SameSite=Lax` cookie. This gives us
|
|
8
|
+
* tamper-proof cookies without requiring any external crypto library.
|
|
9
|
+
*
|
|
10
|
+
* **Security model:**
|
|
11
|
+
* - The cookie is `HttpOnly` — JavaScript in the browser cannot read it.
|
|
12
|
+
* - A separate HMAC signature cookie prevents tampering.
|
|
13
|
+
* - Token validation always happens server-side via {@link CasClient}.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
|
|
16
|
+
import type { CasUser, CasSessionData } from '../types';
|
|
17
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
18
|
+
/**
|
|
19
|
+
* Read the CAS session from Next.js **request** cookies.
|
|
20
|
+
*
|
|
21
|
+
* Works in Server Components, Route Handlers, and middleware — anywhere
|
|
22
|
+
* you have access to the Next.js `cookies()` helper.
|
|
23
|
+
*
|
|
24
|
+
* @param cookies - The Next.js `cookies()` read-only store.
|
|
25
|
+
* @returns The session data, or `null` if no valid session exists.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { cookies } from 'next/headers';
|
|
30
|
+
* import { getCasSession } from '@cas-system/nextjs-cas-client/server';
|
|
31
|
+
*
|
|
32
|
+
* export default async function DashboardPage() {
|
|
33
|
+
* const session = await getCasSession(cookies());
|
|
34
|
+
* if (!session) redirect('/login');
|
|
35
|
+
* return <h1>Hello {session.user.username}</h1>;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function getCasSession(cookies: ReadonlyRequestCookies): Promise<CasSessionData | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Write the CAS session into Next.js **response** cookies.
|
|
42
|
+
*
|
|
43
|
+
* @param cookies - The Next.js `cookies()` store (must be writable — only
|
|
44
|
+
* available inside Route Handlers and Server Actions).
|
|
45
|
+
* @param user - The authenticated user.
|
|
46
|
+
* @param token - The raw JWT token.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { cookies } from 'next/headers';
|
|
51
|
+
* import { setCasSession } from '@cas-system/nextjs-cas-client/server';
|
|
52
|
+
*
|
|
53
|
+
* await setCasSession(cookies(), user, token);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function setCasSession(cookies: ReadonlyRequestCookies, user: CasUser, token: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Delete the CAS session cookies.
|
|
59
|
+
*
|
|
60
|
+
* @param cookies - The Next.js `cookies()` store.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { cookies } from 'next/headers';
|
|
65
|
+
* import { clearCasSession } from '@cas-system/nextjs-cas-client/server';
|
|
66
|
+
*
|
|
67
|
+
* clearCasSession(cookies());
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function clearCasSession(cookies: ReadonlyRequestCookies): void;
|
|
71
|
+
/** Typed Next.js App Router route handler signature. */
|
|
72
|
+
type RouteHandler = (req: NextRequest, ctx?: {
|
|
73
|
+
params?: Record<string, string>;
|
|
74
|
+
}) => Promise<NextResponse | Response> | NextResponse | Response;
|
|
75
|
+
/** A route handler that receives the authenticated user as a 3rd argument. */
|
|
76
|
+
type AuthenticatedHandler = (req: NextRequest, ctx: {
|
|
77
|
+
params?: Record<string, string>;
|
|
78
|
+
}, user: CasUser) => Promise<NextResponse | Response> | NextResponse | Response;
|
|
79
|
+
/**
|
|
80
|
+
* Higher-order function that wraps a Next.js App Router route handler,
|
|
81
|
+
* automatically extracting and verifying the CAS session and injecting the
|
|
82
|
+
* `CasUser` into the handler.
|
|
83
|
+
*
|
|
84
|
+
* If no valid session exists, responds with `401 Unauthorized`.
|
|
85
|
+
*
|
|
86
|
+
* @param handler - The protected route handler.
|
|
87
|
+
* @returns A standard Next.js route handler.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* // app/api/admin/route.ts
|
|
92
|
+
* import { withCasAuth } from '@cas-system/nextjs-cas-client/server';
|
|
93
|
+
*
|
|
94
|
+
* export const GET = withCasAuth(async (req, ctx, user) => {
|
|
95
|
+
* return NextResponse.json({ message: `Hello ${user.username}` });
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function withCasAuth(handler: AuthenticatedHandler): RouteHandler;
|
|
100
|
+
/**
|
|
101
|
+
* Read the CAS session from a plain `NextRequest` (useful in middleware
|
|
102
|
+
* and edge functions where the `cookies()` helper is not available).
|
|
103
|
+
*
|
|
104
|
+
* @param request - The incoming Next.js request.
|
|
105
|
+
* @returns Session data or `null`.
|
|
106
|
+
*
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
export declare function getCasSessionFromRequest(request: NextRequest): Promise<CasSessionData | null>;
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAC;AAC3G,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA8DxD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAuBhC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAGrE;AAMD,wDAAwD;AACxD,KAAK,YAAY,GAAG,CAClB,GAAG,EAAE,WAAW,EAChB,GAAG,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KACtC,OAAO,CAAC,YAAY,GAAG,QAAQ,CAAC,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEhE,8EAA8E;AAC9E,KAAK,oBAAoB,GAAG,CAC1B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EACxC,IAAI,EAAE,OAAO,KACV,OAAO,CAAC,YAAY,GAAG,QAAQ,CAAC,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,YAAY,CAsCvE;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAehC"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/server/auth
|
|
3
|
+
* @description Server-side session helpers for reading / writing CAS session
|
|
4
|
+
* data to Next.js cookies.
|
|
5
|
+
*
|
|
6
|
+
* The session is stored as a base-64 encoded JSON blob (signed with HMAC)
|
|
7
|
+
* inside an `HttpOnly`, `Secure`, `SameSite=Lax` cookie. This gives us
|
|
8
|
+
* tamper-proof cookies without requiring any external crypto library.
|
|
9
|
+
*
|
|
10
|
+
* **Security model:**
|
|
11
|
+
* - The cookie is `HttpOnly` — JavaScript in the browser cannot read it.
|
|
12
|
+
* - A separate HMAC signature cookie prevents tampering.
|
|
13
|
+
* - Token validation always happens server-side via {@link CasClient}.
|
|
14
|
+
*/
|
|
15
|
+
import { NextResponse } from 'next/server';
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Constants
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/** Name of the cookie that stores the session payload. */
|
|
20
|
+
const SESSION_COOKIE = 'cas_session';
|
|
21
|
+
/** Name of the cookie that stores the HMAC signature of the payload. */
|
|
22
|
+
const SIGNATURE_COOKIE = 'cas_session_sig';
|
|
23
|
+
/** Secret used for HMAC signing — falls back to a build-time constant. */
|
|
24
|
+
function getSigningSecret() {
|
|
25
|
+
const secret = process.env.CAS_COOKIE_SECRET ?? process.env.CAS_CLIENT_SECRET;
|
|
26
|
+
if (!secret) {
|
|
27
|
+
throw new Error('[CasAuth] Neither CAS_COOKIE_SECRET nor CAS_CLIENT_SECRET environment variable is set. ' +
|
|
28
|
+
'One of these is required for cookie signing.');
|
|
29
|
+
}
|
|
30
|
+
return secret;
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Internal crypto helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/** @internal */
|
|
36
|
+
async function signPayload(payload, secret) {
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
39
|
+
const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(payload));
|
|
40
|
+
return btoa(String.fromCharCode(...new Uint8Array(sig)));
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
async function verifyPayload(payload, signature, secret) {
|
|
44
|
+
const expected = await signPayload(payload, secret);
|
|
45
|
+
// Constant-time-ish comparison (good enough for HMAC output).
|
|
46
|
+
if (expected.length !== signature.length)
|
|
47
|
+
return false;
|
|
48
|
+
let mismatch = 0;
|
|
49
|
+
for (let i = 0; i < expected.length; i++) {
|
|
50
|
+
mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
51
|
+
}
|
|
52
|
+
return mismatch === 0;
|
|
53
|
+
}
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Public API
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
/**
|
|
58
|
+
* Read the CAS session from Next.js **request** cookies.
|
|
59
|
+
*
|
|
60
|
+
* Works in Server Components, Route Handlers, and middleware — anywhere
|
|
61
|
+
* you have access to the Next.js `cookies()` helper.
|
|
62
|
+
*
|
|
63
|
+
* @param cookies - The Next.js `cookies()` read-only store.
|
|
64
|
+
* @returns The session data, or `null` if no valid session exists.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { cookies } from 'next/headers';
|
|
69
|
+
* import { getCasSession } from '@cas-system/nextjs-cas-client/server';
|
|
70
|
+
*
|
|
71
|
+
* export default async function DashboardPage() {
|
|
72
|
+
* const session = await getCasSession(cookies());
|
|
73
|
+
* if (!session) redirect('/login');
|
|
74
|
+
* return <h1>Hello {session.user.username}</h1>;
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export async function getCasSession(cookies) {
|
|
79
|
+
try {
|
|
80
|
+
const sessionCookie = cookies.get(SESSION_COOKIE);
|
|
81
|
+
const sigCookie = cookies.get(SIGNATURE_COOKIE);
|
|
82
|
+
if (!sessionCookie?.value || !sigCookie?.value)
|
|
83
|
+
return null;
|
|
84
|
+
const secret = getSigningSecret();
|
|
85
|
+
const valid = await verifyPayload(sessionCookie.value, sigCookie.value, secret);
|
|
86
|
+
if (!valid) {
|
|
87
|
+
console.warn('[CasAuth] Cookie signature mismatch — session rejected.');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const decoded = atob(sessionCookie.value);
|
|
91
|
+
const data = JSON.parse(decoded);
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Write the CAS session into Next.js **response** cookies.
|
|
100
|
+
*
|
|
101
|
+
* @param cookies - The Next.js `cookies()` store (must be writable — only
|
|
102
|
+
* available inside Route Handlers and Server Actions).
|
|
103
|
+
* @param user - The authenticated user.
|
|
104
|
+
* @param token - The raw JWT token.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* import { cookies } from 'next/headers';
|
|
109
|
+
* import { setCasSession } from '@cas-system/nextjs-cas-client/server';
|
|
110
|
+
*
|
|
111
|
+
* await setCasSession(cookies(), user, token);
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export async function setCasSession(cookies, user, token) {
|
|
115
|
+
const data = { user, token, createdAt: Date.now() };
|
|
116
|
+
const payload = btoa(JSON.stringify(data));
|
|
117
|
+
const secret = getSigningSecret();
|
|
118
|
+
const sig = await signPayload(payload, secret);
|
|
119
|
+
const cookieOptions = {
|
|
120
|
+
httpOnly: true,
|
|
121
|
+
secure: process.env.NODE_ENV === 'production',
|
|
122
|
+
sameSite: 'lax',
|
|
123
|
+
path: '/',
|
|
124
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
125
|
+
};
|
|
126
|
+
cookies.set(SESSION_COOKIE, payload, cookieOptions);
|
|
127
|
+
cookies.set(SIGNATURE_COOKIE, sig, cookieOptions);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Delete the CAS session cookies.
|
|
131
|
+
*
|
|
132
|
+
* @param cookies - The Next.js `cookies()` store.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { cookies } from 'next/headers';
|
|
137
|
+
* import { clearCasSession } from '@cas-system/nextjs-cas-client/server';
|
|
138
|
+
*
|
|
139
|
+
* clearCasSession(cookies());
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function clearCasSession(cookies) {
|
|
143
|
+
cookies.delete(SESSION_COOKIE);
|
|
144
|
+
cookies.delete(SIGNATURE_COOKIE);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Higher-order function that wraps a Next.js App Router route handler,
|
|
148
|
+
* automatically extracting and verifying the CAS session and injecting the
|
|
149
|
+
* `CasUser` into the handler.
|
|
150
|
+
*
|
|
151
|
+
* If no valid session exists, responds with `401 Unauthorized`.
|
|
152
|
+
*
|
|
153
|
+
* @param handler - The protected route handler.
|
|
154
|
+
* @returns A standard Next.js route handler.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* // app/api/admin/route.ts
|
|
159
|
+
* import { withCasAuth } from '@cas-system/nextjs-cas-client/server';
|
|
160
|
+
*
|
|
161
|
+
* export const GET = withCasAuth(async (req, ctx, user) => {
|
|
162
|
+
* return NextResponse.json({ message: `Hello ${user.username}` });
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function withCasAuth(handler) {
|
|
167
|
+
return async (req, ctx) => {
|
|
168
|
+
// In App Router route handlers, we can read cookies directly from the request.
|
|
169
|
+
const sessionCookie = req.cookies.get(SESSION_COOKIE);
|
|
170
|
+
const sigCookie = req.cookies.get(SIGNATURE_COOKIE);
|
|
171
|
+
if (!sessionCookie?.value || !sigCookie?.value) {
|
|
172
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const secret = getSigningSecret();
|
|
176
|
+
const valid = await verifyPayload(sessionCookie.value, sigCookie.value, secret);
|
|
177
|
+
if (!valid) {
|
|
178
|
+
return NextResponse.json({ error: 'Unauthorized — invalid session' }, { status: 401 });
|
|
179
|
+
}
|
|
180
|
+
const decoded = atob(sessionCookie.value);
|
|
181
|
+
const data = JSON.parse(decoded);
|
|
182
|
+
return handler(req, ctx ?? {}, data.user);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return NextResponse.json({ error: 'Unauthorized — session parse error' }, { status: 401 });
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read the CAS session from a plain `NextRequest` (useful in middleware
|
|
191
|
+
* and edge functions where the `cookies()` helper is not available).
|
|
192
|
+
*
|
|
193
|
+
* @param request - The incoming Next.js request.
|
|
194
|
+
* @returns Session data or `null`.
|
|
195
|
+
*
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
export async function getCasSessionFromRequest(request) {
|
|
199
|
+
try {
|
|
200
|
+
const sessionValue = request.cookies.get(SESSION_COOKIE)?.value;
|
|
201
|
+
const sigValue = request.cookies.get(SIGNATURE_COOKIE)?.value;
|
|
202
|
+
if (!sessionValue || !sigValue)
|
|
203
|
+
return null;
|
|
204
|
+
const secret = getSigningSecret();
|
|
205
|
+
const valid = await verifyPayload(sessionValue, sigValue, secret);
|
|
206
|
+
if (!valid)
|
|
207
|
+
return null;
|
|
208
|
+
const decoded = atob(sessionValue);
|
|
209
|
+
return JSON.parse(decoded);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/server/cas-client
|
|
3
|
+
* @description Server-side CAS client for token validation, generation, and
|
|
4
|
+
* SSO login URL construction. Uses only the built-in `fetch` API — zero
|
|
5
|
+
* external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* **IMPORTANT:** This module must only run on the server. It requires the
|
|
8
|
+
* `clientSecret` which must never be sent to the browser.
|
|
9
|
+
*/
|
|
10
|
+
import type { CasServerConfig, CasUser } from '../types';
|
|
11
|
+
/**
|
|
12
|
+
* Server-side CAS client.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { CasClient } from '@cas-system/nextjs-cas-client/server';
|
|
17
|
+
*
|
|
18
|
+
* const cas = new CasClient({
|
|
19
|
+
* serverUrl: process.env.CAS_SERVER_URL!,
|
|
20
|
+
* clientId: process.env.CAS_CLIENT_ID!,
|
|
21
|
+
* clientSecret: process.env.CAS_CLIENT_SECRET!,
|
|
22
|
+
* callbackUrl: process.env.CAS_CALLBACK_URL,
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Validate a token received from the callback
|
|
26
|
+
* const user = await cas.validateToken(token);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare class CasClient {
|
|
30
|
+
private readonly config;
|
|
31
|
+
constructor(config: CasServerConfig);
|
|
32
|
+
/**
|
|
33
|
+
* Build the CAS SSO login URL.
|
|
34
|
+
*
|
|
35
|
+
* @param returnUrl - Optional URL to redirect back to after login.
|
|
36
|
+
* Falls back to `config.callbackUrl`.
|
|
37
|
+
* @returns Fully-qualified CAS login URL.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const loginUrl = cas.getLoginUrl('/dashboard');
|
|
42
|
+
* redirect(loginUrl);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
getLoginUrl(returnUrl?: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Validate a JWT token with the CAS server (server-to-server call).
|
|
48
|
+
*
|
|
49
|
+
* @param token - The JWT token received from the CAS callback.
|
|
50
|
+
* @returns The authenticated {@link CasUser} or `null` if invalid.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const user = await cas.validateToken(token);
|
|
55
|
+
* if (!user) throw new Error('Invalid token');
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
validateToken(token: string): Promise<CasUser | null>;
|
|
59
|
+
/**
|
|
60
|
+
* Generate a JWT token for the given username (server-to-server).
|
|
61
|
+
*
|
|
62
|
+
* This is useful for machine-to-machine auth flows or impersonation
|
|
63
|
+
* when the calling service already knows the target user.
|
|
64
|
+
*
|
|
65
|
+
* @param username - The username to issue a token for.
|
|
66
|
+
* @returns The JWT string or `null` on failure.
|
|
67
|
+
*/
|
|
68
|
+
generateToken(username: string): Promise<string | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Notify the CAS server of a logout event.
|
|
71
|
+
*
|
|
72
|
+
* @param token - Optional token to invalidate on the server.
|
|
73
|
+
* @returns `true` if the server acknowledged the logout.
|
|
74
|
+
*/
|
|
75
|
+
logout(token?: string): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Check whether a user has **all** of the specified roles.
|
|
78
|
+
*
|
|
79
|
+
* @param user - The CAS user.
|
|
80
|
+
* @param roles - Required roles.
|
|
81
|
+
* @returns `true` if the user possesses every role.
|
|
82
|
+
*/
|
|
83
|
+
static hasRoles(user: CasUser, roles: string[]): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Check whether a user has **at least one** of the specified roles.
|
|
86
|
+
*
|
|
87
|
+
* @param user - The CAS user.
|
|
88
|
+
* @param roles - Roles to check.
|
|
89
|
+
* @returns `true` if the user possesses any of the roles.
|
|
90
|
+
*/
|
|
91
|
+
static hasAnyRole(user: CasUser, roles: string[]): boolean;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=cas-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cas-client.d.ts","sourceRoot":"","sources":["../../src/server/cas-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,OAAO,EAGR,MAAM,UAAU,CAAC;AAkBlB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;gBAErD,MAAM,EAAE,eAAe;IAgBnC;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAevC;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAuC3D;;;;;;;;OAQG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuC7D;;;;;OAKG;IACG,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgC9C;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAKxD;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;CAI3D"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/server/cas-client
|
|
3
|
+
* @description Server-side CAS client for token validation, generation, and
|
|
4
|
+
* SSO login URL construction. Uses only the built-in `fetch` API — zero
|
|
5
|
+
* external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* **IMPORTANT:** This module must only run on the server. It requires the
|
|
8
|
+
* `clientSecret` which must never be sent to the browser.
|
|
9
|
+
*/
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/**
|
|
14
|
+
* Normalise a base URL by stripping a trailing slash.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
function normaliseUrl(url) {
|
|
18
|
+
return url.replace(/\/+$/, '');
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// CasClient
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Server-side CAS client.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { CasClient } from '@cas-system/nextjs-cas-client/server';
|
|
29
|
+
*
|
|
30
|
+
* const cas = new CasClient({
|
|
31
|
+
* serverUrl: process.env.CAS_SERVER_URL!,
|
|
32
|
+
* clientId: process.env.CAS_CLIENT_ID!,
|
|
33
|
+
* clientSecret: process.env.CAS_CLIENT_SECRET!,
|
|
34
|
+
* callbackUrl: process.env.CAS_CALLBACK_URL,
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Validate a token received from the callback
|
|
38
|
+
* const user = await cas.validateToken(token);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class CasClient {
|
|
42
|
+
constructor(config) {
|
|
43
|
+
if (!config.serverUrl)
|
|
44
|
+
throw new Error('[CasClient] serverUrl is required');
|
|
45
|
+
if (!config.clientId)
|
|
46
|
+
throw new Error('[CasClient] clientId is required');
|
|
47
|
+
if (!config.clientSecret)
|
|
48
|
+
throw new Error('[CasClient] clientSecret is required');
|
|
49
|
+
this.config = {
|
|
50
|
+
...config,
|
|
51
|
+
serverUrl: normaliseUrl(config.serverUrl),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// -----------------------------------------------------------------------
|
|
55
|
+
// Login URL
|
|
56
|
+
// -----------------------------------------------------------------------
|
|
57
|
+
/**
|
|
58
|
+
* Build the CAS SSO login URL.
|
|
59
|
+
*
|
|
60
|
+
* @param returnUrl - Optional URL to redirect back to after login.
|
|
61
|
+
* Falls back to `config.callbackUrl`.
|
|
62
|
+
* @returns Fully-qualified CAS login URL.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const loginUrl = cas.getLoginUrl('/dashboard');
|
|
67
|
+
* redirect(loginUrl);
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
getLoginUrl(returnUrl) {
|
|
71
|
+
const callbackUrl = returnUrl ?? this.config.callbackUrl ?? '/api/cas/callback';
|
|
72
|
+
const params = new URLSearchParams({
|
|
73
|
+
client_id: this.config.clientId,
|
|
74
|
+
response_type: 'token',
|
|
75
|
+
redirect_uri: callbackUrl,
|
|
76
|
+
});
|
|
77
|
+
return `${this.config.serverUrl}/sso/login?${params.toString()}`;
|
|
78
|
+
}
|
|
79
|
+
// -----------------------------------------------------------------------
|
|
80
|
+
// Token Validation
|
|
81
|
+
// -----------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Validate a JWT token with the CAS server (server-to-server call).
|
|
84
|
+
*
|
|
85
|
+
* @param token - The JWT token received from the CAS callback.
|
|
86
|
+
* @returns The authenticated {@link CasUser} or `null` if invalid.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const user = await cas.validateToken(token);
|
|
91
|
+
* if (!user) throw new Error('Invalid token');
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
async validateToken(token) {
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(`${this.config.serverUrl}/api/validate-token`, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
token,
|
|
103
|
+
client_id: this.config.clientId,
|
|
104
|
+
client_secret: this.config.clientSecret,
|
|
105
|
+
}),
|
|
106
|
+
cache: 'no-store',
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
console.error(`[CasClient] validateToken failed: ${res.status} ${res.statusText}`);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const data = (await res.json());
|
|
113
|
+
if (!data.valid || !data.user)
|
|
114
|
+
return null;
|
|
115
|
+
return data.user;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.error('[CasClient] validateToken error:', err);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// -----------------------------------------------------------------------
|
|
123
|
+
// Token Generation
|
|
124
|
+
// -----------------------------------------------------------------------
|
|
125
|
+
/**
|
|
126
|
+
* Generate a JWT token for the given username (server-to-server).
|
|
127
|
+
*
|
|
128
|
+
* This is useful for machine-to-machine auth flows or impersonation
|
|
129
|
+
* when the calling service already knows the target user.
|
|
130
|
+
*
|
|
131
|
+
* @param username - The username to issue a token for.
|
|
132
|
+
* @returns The JWT string or `null` on failure.
|
|
133
|
+
*/
|
|
134
|
+
async generateToken(username) {
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch(`${this.config.serverUrl}/api/sso/token`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: {
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
},
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
client_id: this.config.clientId,
|
|
143
|
+
client_secret: this.config.clientSecret,
|
|
144
|
+
username,
|
|
145
|
+
}),
|
|
146
|
+
cache: 'no-store',
|
|
147
|
+
});
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
console.error(`[CasClient] generateToken failed: ${res.status} ${res.statusText}`);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const data = (await res.json());
|
|
153
|
+
if (!data.token)
|
|
154
|
+
return null;
|
|
155
|
+
return data.token;
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error('[CasClient] generateToken error:', err);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// -----------------------------------------------------------------------
|
|
163
|
+
// Logout
|
|
164
|
+
// -----------------------------------------------------------------------
|
|
165
|
+
/**
|
|
166
|
+
* Notify the CAS server of a logout event.
|
|
167
|
+
*
|
|
168
|
+
* @param token - Optional token to invalidate on the server.
|
|
169
|
+
* @returns `true` if the server acknowledged the logout.
|
|
170
|
+
*/
|
|
171
|
+
async logout(token) {
|
|
172
|
+
try {
|
|
173
|
+
const headers = {
|
|
174
|
+
'Content-Type': 'application/json',
|
|
175
|
+
};
|
|
176
|
+
if (token) {
|
|
177
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
178
|
+
}
|
|
179
|
+
const res = await fetch(`${this.config.serverUrl}/api/logout`, {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers,
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
client_id: this.config.clientId,
|
|
184
|
+
}),
|
|
185
|
+
cache: 'no-store',
|
|
186
|
+
});
|
|
187
|
+
return res.ok;
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
console.error('[CasClient] logout error:', err);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// -----------------------------------------------------------------------
|
|
195
|
+
// Role helpers
|
|
196
|
+
// -----------------------------------------------------------------------
|
|
197
|
+
/**
|
|
198
|
+
* Check whether a user has **all** of the specified roles.
|
|
199
|
+
*
|
|
200
|
+
* @param user - The CAS user.
|
|
201
|
+
* @param roles - Required roles.
|
|
202
|
+
* @returns `true` if the user possesses every role.
|
|
203
|
+
*/
|
|
204
|
+
static hasRoles(user, roles) {
|
|
205
|
+
if (!user.roles)
|
|
206
|
+
return roles.length === 0;
|
|
207
|
+
return roles.every((r) => user.roles.includes(r));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check whether a user has **at least one** of the specified roles.
|
|
211
|
+
*
|
|
212
|
+
* @param user - The CAS user.
|
|
213
|
+
* @param roles - Roles to check.
|
|
214
|
+
* @returns `true` if the user possesses any of the roles.
|
|
215
|
+
*/
|
|
216
|
+
static hasAnyRole(user, roles) {
|
|
217
|
+
if (!user.roles)
|
|
218
|
+
return false;
|
|
219
|
+
return roles.some((r) => user.roles.includes(r));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=cas-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cas-client.js","sourceRoot":"","sources":["../../src/server/cas-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/server
|
|
3
|
+
* @description Server-only entry point for the Next.js CAS Client SDK.
|
|
4
|
+
*
|
|
5
|
+
* Exposes the {@link CasClient} (token validation / generation / logout) and
|
|
6
|
+
* the cookie-based session helpers. These modules require the
|
|
7
|
+
* `clientSecret` / cookie signing secret and must never run in the browser.
|
|
8
|
+
*/
|
|
9
|
+
export { CasClient } from './cas-client';
|
|
10
|
+
export { getCasSession, setCasSession, clearCasSession, withCasAuth, getCasSessionFromRequest, } from './auth';
|
|
11
|
+
export type { CasConfig, CasServerConfig, CasUser, CasSessionData, CasHandlerConfig, } from '../types';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|