@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,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers/callback
|
|
3
|
+
* @description API route handler for the CAS SSO callback endpoint.
|
|
4
|
+
*
|
|
5
|
+
* After the CAS server authenticates the user it redirects to
|
|
6
|
+
* `{callbackUrl}?token=JWT_TOKEN`. This handler:
|
|
7
|
+
*
|
|
8
|
+
* 1. Extracts the `token` query parameter.
|
|
9
|
+
* 2. Validates it server-side via the CAS `/api/validate-token` endpoint.
|
|
10
|
+
* 3. Sets an encrypted session cookie.
|
|
11
|
+
* 4. Redirects the user to the intended URL (or `/`).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // app/api/cas/callback/route.ts
|
|
16
|
+
* import { createCallbackHandler } from '@cas-system/nextjs-cas-client/handlers';
|
|
17
|
+
*
|
|
18
|
+
* const handler = createCallbackHandler({
|
|
19
|
+
* cas: {
|
|
20
|
+
* serverUrl: process.env.CAS_SERVER_URL!,
|
|
21
|
+
* clientId: process.env.CAS_CLIENT_ID!,
|
|
22
|
+
* clientSecret: process.env.CAS_CLIENT_SECRET!,
|
|
23
|
+
* },
|
|
24
|
+
* afterLoginUrl: '/dashboard',
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* export { handler as GET };
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import { NextResponse } from 'next/server';
|
|
31
|
+
import { cookies } from 'next/headers';
|
|
32
|
+
import { CasClient } from '../server/cas-client';
|
|
33
|
+
import { setCasSession } from '../server/auth';
|
|
34
|
+
/**
|
|
35
|
+
* Create a GET handler for `/api/cas/callback`.
|
|
36
|
+
*
|
|
37
|
+
* @param config - Handler configuration including CAS server details.
|
|
38
|
+
* @returns A Next.js App Router GET handler.
|
|
39
|
+
*/
|
|
40
|
+
export function createCallbackHandler(config) {
|
|
41
|
+
const cas = new CasClient(config.cas);
|
|
42
|
+
const afterLoginUrl = config.afterLoginUrl ?? '/';
|
|
43
|
+
return async function GET(request) {
|
|
44
|
+
const { searchParams } = request.nextUrl;
|
|
45
|
+
const token = searchParams.get('token');
|
|
46
|
+
const returnUrl = searchParams.get('returnUrl') ?? afterLoginUrl;
|
|
47
|
+
if (!token) {
|
|
48
|
+
console.error('[CasCallback] No token query parameter received.');
|
|
49
|
+
return NextResponse.redirect(new URL('/login?error=no_token', request.url));
|
|
50
|
+
}
|
|
51
|
+
// Validate the token server-side
|
|
52
|
+
const user = await cas.validateToken(token);
|
|
53
|
+
if (!user) {
|
|
54
|
+
console.error('[CasCallback] Token validation failed.');
|
|
55
|
+
return NextResponse.redirect(new URL('/login?error=invalid_token', request.url));
|
|
56
|
+
}
|
|
57
|
+
// Set session cookie
|
|
58
|
+
const cookieStore = await cookies();
|
|
59
|
+
await setCasSession(cookieStore, user, token);
|
|
60
|
+
// Redirect to the intended URL
|
|
61
|
+
const redirectUrl = new URL(returnUrl, request.url);
|
|
62
|
+
return NextResponse.redirect(redirectUrl);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/handlers/callback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAwB;IAC5D,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,GAAG,CAAC;IAElD,OAAO,KAAK,UAAU,GAAG,CAAC,OAAoB;QAC5C,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;QACzC,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,aAAa,CAAC;QAEjE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;YAClE,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,uBAAuB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9E,CAAC;QAED,iCAAiC;QACjC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAE5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YACxD,OAAO,YAAY,CAAC,QAAQ,CAC1B,IAAI,GAAG,CAAC,4BAA4B,EAAE,OAAO,CAAC,GAAG,CAAC,CACnD,CAAC;QACJ,CAAC;QAED,qBAAqB;QACrB,MAAM,WAAW,GAAG,MAAM,OAAO,EAAE,CAAC;QACpC,MAAM,aAAa,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAE9C,+BAA+B;QAC/B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACpD,OAAO,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers
|
|
3
|
+
* @description Re-exports all API route handler factories.
|
|
4
|
+
*/
|
|
5
|
+
export { createCallbackHandler } from './callback';
|
|
6
|
+
export { createLogoutHandler } from './logout';
|
|
7
|
+
export { createUserHandler } from './user';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers
|
|
3
|
+
* @description Re-exports all API route handler factories.
|
|
4
|
+
*/
|
|
5
|
+
export { createCallbackHandler } from './callback';
|
|
6
|
+
export { createLogoutHandler } from './logout';
|
|
7
|
+
export { createUserHandler } from './user';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers/logout
|
|
3
|
+
* @description API route handler for the CAS logout endpoint.
|
|
4
|
+
*
|
|
5
|
+
* 1. Reads the session to obtain the JWT token.
|
|
6
|
+
* 2. Notifies the CAS server of the logout.
|
|
7
|
+
* 3. Clears the session cookies.
|
|
8
|
+
* 4. Redirects the user (or returns 200 for API calls).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // app/api/cas/logout/route.ts
|
|
13
|
+
* import { createLogoutHandler } from '@cas-system/nextjs-cas-client/handlers';
|
|
14
|
+
*
|
|
15
|
+
* const handler = createLogoutHandler({
|
|
16
|
+
* cas: {
|
|
17
|
+
* serverUrl: process.env.CAS_SERVER_URL!,
|
|
18
|
+
* clientId: process.env.CAS_CLIENT_ID!,
|
|
19
|
+
* clientSecret: process.env.CAS_CLIENT_SECRET!,
|
|
20
|
+
* },
|
|
21
|
+
* afterLogoutUrl: '/',
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* export { handler as POST };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
28
|
+
import type { CasHandlerConfig } from '../types';
|
|
29
|
+
/**
|
|
30
|
+
* Create a POST handler for `/api/cas/logout`.
|
|
31
|
+
*
|
|
32
|
+
* @param config - Handler configuration.
|
|
33
|
+
* @returns A Next.js App Router POST handler.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createLogoutHandler(config: CasHandlerConfig): (request: NextRequest) => Promise<NextResponse>;
|
|
36
|
+
//# sourceMappingURL=logout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/handlers/logout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAIjD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,IAI/B,SAAS,WAAW,KAAG,OAAO,CAAC,YAAY,CAAC,CAyBxE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers/logout
|
|
3
|
+
* @description API route handler for the CAS logout endpoint.
|
|
4
|
+
*
|
|
5
|
+
* 1. Reads the session to obtain the JWT token.
|
|
6
|
+
* 2. Notifies the CAS server of the logout.
|
|
7
|
+
* 3. Clears the session cookies.
|
|
8
|
+
* 4. Redirects the user (or returns 200 for API calls).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // app/api/cas/logout/route.ts
|
|
13
|
+
* import { createLogoutHandler } from '@cas-system/nextjs-cas-client/handlers';
|
|
14
|
+
*
|
|
15
|
+
* const handler = createLogoutHandler({
|
|
16
|
+
* cas: {
|
|
17
|
+
* serverUrl: process.env.CAS_SERVER_URL!,
|
|
18
|
+
* clientId: process.env.CAS_CLIENT_ID!,
|
|
19
|
+
* clientSecret: process.env.CAS_CLIENT_SECRET!,
|
|
20
|
+
* },
|
|
21
|
+
* afterLogoutUrl: '/',
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* export { handler as POST };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import { NextResponse } from 'next/server';
|
|
28
|
+
import { cookies } from 'next/headers';
|
|
29
|
+
import { CasClient } from '../server/cas-client';
|
|
30
|
+
import { getCasSession, clearCasSession } from '../server/auth';
|
|
31
|
+
/**
|
|
32
|
+
* Create a POST handler for `/api/cas/logout`.
|
|
33
|
+
*
|
|
34
|
+
* @param config - Handler configuration.
|
|
35
|
+
* @returns A Next.js App Router POST handler.
|
|
36
|
+
*/
|
|
37
|
+
export function createLogoutHandler(config) {
|
|
38
|
+
const cas = new CasClient(config.cas);
|
|
39
|
+
const afterLogoutUrl = config.afterLogoutUrl ?? '/';
|
|
40
|
+
return async function POST(request) {
|
|
41
|
+
const cookieStore = await cookies();
|
|
42
|
+
// Attempt to read the current session for the token
|
|
43
|
+
const session = await getCasSession(cookieStore);
|
|
44
|
+
// Notify the CAS server (best-effort)
|
|
45
|
+
if (session?.token) {
|
|
46
|
+
await cas.logout(session.token).catch(() => {
|
|
47
|
+
// Swallow — the local session will be cleared regardless
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Clear local session cookies
|
|
51
|
+
clearCasSession(cookieStore);
|
|
52
|
+
// If the request expects JSON (e.g. fetch from client), return 200
|
|
53
|
+
const accept = request.headers.get('accept') ?? '';
|
|
54
|
+
if (accept.includes('application/json')) {
|
|
55
|
+
return NextResponse.json({ success: true });
|
|
56
|
+
}
|
|
57
|
+
// Otherwise redirect
|
|
58
|
+
return NextResponse.redirect(new URL(afterLogoutUrl, request.url));
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/handlers/logout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAwB;IAC1D,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,GAAG,CAAC;IAEpD,OAAO,KAAK,UAAU,IAAI,CAAC,OAAoB;QAC7C,MAAM,WAAW,GAAG,MAAM,OAAO,EAAE,CAAC;QAEpC,oDAAoD;QACpD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;QAEjD,sCAAsC;QACtC,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YACnB,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBACzC,yDAAyD;YAC3D,CAAC,CAAC,CAAC;QACL,CAAC;QAED,8BAA8B;QAC9B,eAAe,CAAC,WAAW,CAAC,CAAC;QAE7B,mEAAmE;QACnE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACxC,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;QAED,qBAAqB;QACrB,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers/user
|
|
3
|
+
* @description API route handler that returns the current CAS user from
|
|
4
|
+
* the session cookie. This endpoint is called by the client-side
|
|
5
|
+
* `CasProvider` to hydrate the auth state.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // app/api/cas/user/route.ts
|
|
10
|
+
* import { createUserHandler } from '@cas-system/nextjs-cas-client/handlers';
|
|
11
|
+
*
|
|
12
|
+
* const handler = createUserHandler();
|
|
13
|
+
* export { handler as GET };
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { NextResponse } from 'next/server';
|
|
17
|
+
/**
|
|
18
|
+
* Create a GET handler for `/api/cas/user`.
|
|
19
|
+
*
|
|
20
|
+
* Returns `{ user: CasUser }` when authenticated, or `401` otherwise.
|
|
21
|
+
* The response includes `Cache-Control: no-store` to prevent caching of
|
|
22
|
+
* auth state.
|
|
23
|
+
*
|
|
24
|
+
* @returns A Next.js App Router GET handler.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createUserHandler(): () => Promise<NextResponse>;
|
|
27
|
+
//# sourceMappingURL=user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/handlers/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,UACF,OAAO,CAAC,YAAY,CAAC,CAsBnD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/handlers/user
|
|
3
|
+
* @description API route handler that returns the current CAS user from
|
|
4
|
+
* the session cookie. This endpoint is called by the client-side
|
|
5
|
+
* `CasProvider` to hydrate the auth state.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // app/api/cas/user/route.ts
|
|
10
|
+
* import { createUserHandler } from '@cas-system/nextjs-cas-client/handlers';
|
|
11
|
+
*
|
|
12
|
+
* const handler = createUserHandler();
|
|
13
|
+
* export { handler as GET };
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { NextResponse } from 'next/server';
|
|
17
|
+
import { cookies } from 'next/headers';
|
|
18
|
+
import { getCasSession } from '../server/auth';
|
|
19
|
+
/**
|
|
20
|
+
* Create a GET handler for `/api/cas/user`.
|
|
21
|
+
*
|
|
22
|
+
* Returns `{ user: CasUser }` when authenticated, or `401` otherwise.
|
|
23
|
+
* The response includes `Cache-Control: no-store` to prevent caching of
|
|
24
|
+
* auth state.
|
|
25
|
+
*
|
|
26
|
+
* @returns A Next.js App Router GET handler.
|
|
27
|
+
*/
|
|
28
|
+
export function createUserHandler() {
|
|
29
|
+
return async function GET() {
|
|
30
|
+
const cookieStore = await cookies();
|
|
31
|
+
const session = await getCasSession(cookieStore);
|
|
32
|
+
if (!session) {
|
|
33
|
+
return NextResponse.json({ user: null, error: 'Not authenticated' }, {
|
|
34
|
+
status: 401,
|
|
35
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return NextResponse.json({ user: session.user }, {
|
|
39
|
+
status: 200,
|
|
40
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/handlers/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,KAAK,UAAU,GAAG;QACvB,MAAM,WAAW,GAAG,MAAM,OAAO,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;QAEjD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,YAAY,CAAC,IAAI,CACtB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAC1C;gBACE,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE;aACzC,CACF,CAAC;QACJ,CAAC;QAED,OAAO,YAAY,CAAC,IAAI,CACtB,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EACtB;YACE,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE;SACzC,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/hooks/useCasAuth
|
|
3
|
+
* @description Client-side hook for accessing CAS authentication state and
|
|
4
|
+
* actions (login, logout, refresh).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* 'use client';
|
|
9
|
+
* import { useCasAuth } from '@cas-system/nextjs-cas-client';
|
|
10
|
+
*
|
|
11
|
+
* export function Navbar() {
|
|
12
|
+
* const { user, isAuthenticated, login, logout } = useCasAuth();
|
|
13
|
+
*
|
|
14
|
+
* return isAuthenticated ? (
|
|
15
|
+
* <button onClick={logout}>Sign out ({user?.username})</button>
|
|
16
|
+
* ) : (
|
|
17
|
+
* <button onClick={() => login()}>Sign in</button>
|
|
18
|
+
* );
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { CasAuthContext } from '../types';
|
|
23
|
+
/**
|
|
24
|
+
* Access CAS authentication state and actions.
|
|
25
|
+
*
|
|
26
|
+
* This is a convenience re-export of the context hook. Must be used inside
|
|
27
|
+
* a `<CasProvider>`.
|
|
28
|
+
*
|
|
29
|
+
* @returns The full {@link CasAuthContext}.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useCasAuth(): CasAuthContext;
|
|
32
|
+
//# sourceMappingURL=useCasAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCasAuth.d.ts","sourceRoot":"","sources":["../../src/hooks/useCasAuth.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG/C;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAE3C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCasAuthContext } from '../CasProvider';
|
|
3
|
+
/**
|
|
4
|
+
* Access CAS authentication state and actions.
|
|
5
|
+
*
|
|
6
|
+
* This is a convenience re-export of the context hook. Must be used inside
|
|
7
|
+
* a `<CasProvider>`.
|
|
8
|
+
*
|
|
9
|
+
* @returns The full {@link CasAuthContext}.
|
|
10
|
+
*/
|
|
11
|
+
export function useCasAuth() {
|
|
12
|
+
return useCasAuthContext();
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=useCasAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCasAuth.js","sourceRoot":"","sources":["../../src/hooks/useCasAuth.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAyBb,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,iBAAiB,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CasUser } from '../types';
|
|
2
|
+
/** Return type of {@link useCasUser}. */
|
|
3
|
+
export interface UseCasUserReturn {
|
|
4
|
+
/** The current user, or `null` when not authenticated. */
|
|
5
|
+
user: CasUser | null;
|
|
6
|
+
/** Whether the initial session fetch is still loading. */
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
/** Whether a valid session exists. */
|
|
9
|
+
isAuthenticated: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Check whether the current user has **all** of the specified roles.
|
|
12
|
+
*
|
|
13
|
+
* @param roles - Required roles.
|
|
14
|
+
* @returns `true` if the user possesses every role (or the list is empty).
|
|
15
|
+
*/
|
|
16
|
+
hasRole: (...roles: string[]) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Check whether the current user has **at least one** of the specified roles.
|
|
19
|
+
*
|
|
20
|
+
* @param roles - Roles to check.
|
|
21
|
+
* @returns `true` if the user possesses any one role.
|
|
22
|
+
*/
|
|
23
|
+
hasAnyRole: (...roles: string[]) => boolean;
|
|
24
|
+
/** All roles assigned to the current user (empty array when unauthenticated). */
|
|
25
|
+
roles: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Hook for accessing the current CAS user and performing role checks.
|
|
29
|
+
*
|
|
30
|
+
* Must be used inside a `<CasProvider>`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useCasUser(): UseCasUserReturn;
|
|
33
|
+
//# sourceMappingURL=useCasUser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCasUser.d.ts","sourceRoot":"","sources":["../../src/hooks/useCasUser.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAErB,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAC;IAEnB,sCAAsC;IACtC,eAAe,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,OAAO,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAEzC;;;;;OAKG;IACH,UAAU,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAE5C,iFAAiF;IACjF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CAsB7C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* @module @cas-system/nextjs-cas-client/hooks/useCasUser
|
|
4
|
+
* @description Client-side hook focused on the current CAS user and
|
|
5
|
+
* role-based access control helpers.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* 'use client';
|
|
10
|
+
* import { useCasUser } from '@cas-system/nextjs-cas-client';
|
|
11
|
+
*
|
|
12
|
+
* export function AdminPanel() {
|
|
13
|
+
* const { user, hasRole, hasAnyRole } = useCasUser();
|
|
14
|
+
*
|
|
15
|
+
* if (!hasRole('admin')) {
|
|
16
|
+
* return <p>Access denied</p>;
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* return <h1>Welcome admin {user?.username}</h1>;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { useCallback, useMemo } from 'react';
|
|
24
|
+
import { useCasAuthContext } from '../CasProvider';
|
|
25
|
+
/**
|
|
26
|
+
* Hook for accessing the current CAS user and performing role checks.
|
|
27
|
+
*
|
|
28
|
+
* Must be used inside a `<CasProvider>`.
|
|
29
|
+
*/
|
|
30
|
+
export function useCasUser() {
|
|
31
|
+
const { user, isLoading, isAuthenticated } = useCasAuthContext();
|
|
32
|
+
const roles = useMemo(() => user?.roles ?? [], [user]);
|
|
33
|
+
const hasRole = useCallback((...required) => {
|
|
34
|
+
if (!user?.roles)
|
|
35
|
+
return required.length === 0;
|
|
36
|
+
return required.every((r) => user.roles.includes(r));
|
|
37
|
+
}, [user]);
|
|
38
|
+
const hasAnyRole = useCallback((...candidates) => {
|
|
39
|
+
if (!user?.roles)
|
|
40
|
+
return false;
|
|
41
|
+
return candidates.some((r) => user.roles.includes(r));
|
|
42
|
+
}, [user]);
|
|
43
|
+
return { user, isLoading, isAuthenticated, hasRole, hasAnyRole, roles };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=useCasUser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCasUser.js","sourceRoot":"","sources":["../../src/hooks/useCasUser.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAiCnD;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAEjE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEvD,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,GAAG,QAAkB,EAAE,EAAE;QACxB,IAAI,CAAC,IAAI,EAAE,KAAK;YAAE,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;QAC/C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,EACD,CAAC,IAAI,CAAC,CACP,CAAC;IAEF,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,GAAG,UAAoB,EAAE,EAAE;QAC1B,IAAI,CAAC,IAAI,EAAE,KAAK;YAAE,OAAO,KAAK,CAAC;QAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC,EACD,CAAC,IAAI,CAAC,CACP,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAC1E,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client
|
|
3
|
+
* @description Public entry point for the Next.js CAS Client SDK.
|
|
4
|
+
*
|
|
5
|
+
* Re-exports the client-side provider, hooks, components, and shared types.
|
|
6
|
+
* Server-only helpers live under `@cas-system/nextjs-cas-client/server`,
|
|
7
|
+
* route handlers under `/handlers`, and middleware under `/middleware`.
|
|
8
|
+
*/
|
|
9
|
+
export { CasProvider, useCasAuthContext } from './CasProvider';
|
|
10
|
+
export type { CasProviderProps } from './CasProvider';
|
|
11
|
+
export { useCasAuth } from './hooks/useCasAuth';
|
|
12
|
+
export { useCasUser } from './hooks/useCasUser';
|
|
13
|
+
export type { UseCasUserReturn } from './hooks/useCasUser';
|
|
14
|
+
export { CasLoginButton } from './components/CasLoginButton';
|
|
15
|
+
export type { CasLoginButtonProps } from './components/CasLoginButton';
|
|
16
|
+
export { CasProtectedRoute } from './components/CasProtectedRoute';
|
|
17
|
+
export type { CasProtectedRouteProps } from './components/CasProtectedRoute';
|
|
18
|
+
export type { CasConfig, CasServerConfig, CasUser, CasSessionData, CasMiddlewareConfig, CasAuthContext, CasHandlerConfig, } from './types';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,YAAY,EACV,SAAS,EACT,eAAe,EACf,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client
|
|
3
|
+
* @description Public entry point for the Next.js CAS Client SDK.
|
|
4
|
+
*
|
|
5
|
+
* Re-exports the client-side provider, hooks, components, and shared types.
|
|
6
|
+
* Server-only helpers live under `@cas-system/nextjs-cas-client/server`,
|
|
7
|
+
* route handlers under `/handlers`, and middleware under `/middleware`.
|
|
8
|
+
*/
|
|
9
|
+
// Provider + context hook
|
|
10
|
+
export { CasProvider, useCasAuthContext } from './CasProvider';
|
|
11
|
+
// Hooks
|
|
12
|
+
export { useCasAuth } from './hooks/useCasAuth';
|
|
13
|
+
export { useCasUser } from './hooks/useCasUser';
|
|
14
|
+
// Components
|
|
15
|
+
export { CasLoginButton } from './components/CasLoginButton';
|
|
16
|
+
export { CasProtectedRoute } from './components/CasProtectedRoute';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0BAA0B;AAC1B,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAG/D,QAAQ;AACR,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/middleware
|
|
3
|
+
* @description Factory for creating a Next.js middleware that guards routes
|
|
4
|
+
* behind CAS authentication.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // middleware.ts (project root)
|
|
9
|
+
* import { createCasMiddleware } from '@cas-system/nextjs-cas-client/middleware';
|
|
10
|
+
*
|
|
11
|
+
* export default createCasMiddleware({
|
|
12
|
+
* protectedPaths: ['/dashboard', '/admin', '/settings'],
|
|
13
|
+
* publicPaths: ['/api/health'],
|
|
14
|
+
* loginPath: '/login',
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* export const config = {
|
|
18
|
+
* matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
23
|
+
import type { CasMiddlewareConfig } from './types';
|
|
24
|
+
/**
|
|
25
|
+
* Create a Next.js middleware function that enforces CAS authentication on
|
|
26
|
+
* the configured protected paths.
|
|
27
|
+
*
|
|
28
|
+
* @param config - Middleware configuration.
|
|
29
|
+
* @returns A Next.js-compatible middleware function.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* - If the request targets a **public path** it is always allowed through.
|
|
33
|
+
* - If the request targets a **protected path** and there is no valid CAS
|
|
34
|
+
* session cookie, the user is redirected to either the `loginPath` (if
|
|
35
|
+
* set) or the CAS server SSO login page.
|
|
36
|
+
* - The original URL is forwarded as a `?returnUrl=…` query parameter so
|
|
37
|
+
* the login page can redirect back after authentication.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createCasMiddleware(config: CasMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>;
|
|
40
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAanD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,IAQ3D,SAAS,WAAW,KACnB,OAAO,CAAC,YAAY,CAAC,CA+EzB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @cas-system/nextjs-cas-client/middleware
|
|
3
|
+
* @description Factory for creating a Next.js middleware that guards routes
|
|
4
|
+
* behind CAS authentication.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // middleware.ts (project root)
|
|
9
|
+
* import { createCasMiddleware } from '@cas-system/nextjs-cas-client/middleware';
|
|
10
|
+
*
|
|
11
|
+
* export default createCasMiddleware({
|
|
12
|
+
* protectedPaths: ['/dashboard', '/admin', '/settings'],
|
|
13
|
+
* publicPaths: ['/api/health'],
|
|
14
|
+
* loginPath: '/login',
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* export const config = {
|
|
18
|
+
* matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { NextResponse } from 'next/server';
|
|
23
|
+
import { getCasSessionFromRequest } from './server/auth';
|
|
24
|
+
/**
|
|
25
|
+
* Check whether `pathname` starts with any of the given prefixes.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
function matchesAny(pathname, prefixes) {
|
|
29
|
+
return prefixes.some((p) => pathname === p || pathname.startsWith(p.endsWith('/') ? p : `${p}/`));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a Next.js middleware function that enforces CAS authentication on
|
|
33
|
+
* the configured protected paths.
|
|
34
|
+
*
|
|
35
|
+
* @param config - Middleware configuration.
|
|
36
|
+
* @returns A Next.js-compatible middleware function.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* - If the request targets a **public path** it is always allowed through.
|
|
40
|
+
* - If the request targets a **protected path** and there is no valid CAS
|
|
41
|
+
* session cookie, the user is redirected to either the `loginPath` (if
|
|
42
|
+
* set) or the CAS server SSO login page.
|
|
43
|
+
* - The original URL is forwarded as a `?returnUrl=…` query parameter so
|
|
44
|
+
* the login page can redirect back after authentication.
|
|
45
|
+
*/
|
|
46
|
+
export function createCasMiddleware(config) {
|
|
47
|
+
const { protectedPaths, publicPaths = [], loginPath, } = config;
|
|
48
|
+
return async function casMiddleware(request) {
|
|
49
|
+
const { pathname } = request.nextUrl;
|
|
50
|
+
// 1. Skip static assets and internal Next.js routes
|
|
51
|
+
if (pathname.startsWith('/_next') ||
|
|
52
|
+
pathname.startsWith('/favicon') ||
|
|
53
|
+
pathname.includes('.')) {
|
|
54
|
+
return NextResponse.next();
|
|
55
|
+
}
|
|
56
|
+
// 2. Always allow explicitly public paths
|
|
57
|
+
if (matchesAny(pathname, publicPaths)) {
|
|
58
|
+
return NextResponse.next();
|
|
59
|
+
}
|
|
60
|
+
// 3. Check whether this path requires authentication
|
|
61
|
+
const isProtected = matchesAny(pathname, protectedPaths);
|
|
62
|
+
if (!isProtected) {
|
|
63
|
+
return NextResponse.next();
|
|
64
|
+
}
|
|
65
|
+
// 4. Attempt to read the session from cookies
|
|
66
|
+
const session = await getCasSessionFromRequest(request);
|
|
67
|
+
if (session) {
|
|
68
|
+
// Authenticated — attach user info as request headers so downstream
|
|
69
|
+
// Server Components / Route Handlers can read them without re-parsing
|
|
70
|
+
// the cookie.
|
|
71
|
+
const requestHeaders = new Headers(request.headers);
|
|
72
|
+
requestHeaders.set('x-cas-user', JSON.stringify(session.user));
|
|
73
|
+
requestHeaders.set('x-cas-authenticated', 'true');
|
|
74
|
+
return NextResponse.next({
|
|
75
|
+
request: { headers: requestHeaders },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// 5. Not authenticated — redirect to login
|
|
79
|
+
const returnUrl = request.nextUrl.pathname + request.nextUrl.search;
|
|
80
|
+
if (loginPath) {
|
|
81
|
+
// Redirect to the app's own login page
|
|
82
|
+
const loginUrl = new URL(loginPath, request.url);
|
|
83
|
+
loginUrl.searchParams.set('returnUrl', returnUrl);
|
|
84
|
+
return NextResponse.redirect(loginUrl);
|
|
85
|
+
}
|
|
86
|
+
// Redirect directly to the CAS SSO login page.
|
|
87
|
+
// The browser must reach CAS at its PUBLIC base url, which may differ from
|
|
88
|
+
// the internal back-channel url used for server-to-server validation.
|
|
89
|
+
// Prefer CAS_PUBLIC_URL; fall back to CAS_SERVER_URL for single-url setups.
|
|
90
|
+
const casServerUrl = process.env.CAS_PUBLIC_URL || process.env.CAS_SERVER_URL;
|
|
91
|
+
const casClientId = process.env.CAS_CLIENT_ID;
|
|
92
|
+
const casCallbackUrl = process.env.CAS_CALLBACK_URL ??
|
|
93
|
+
`${request.nextUrl.origin}/api/cas/callback`;
|
|
94
|
+
if (!casServerUrl || !casClientId) {
|
|
95
|
+
console.error('[CasMiddleware] CAS_SERVER_URL and CAS_CLIENT_ID env vars are required ' +
|
|
96
|
+
'when no loginPath is configured.');
|
|
97
|
+
return NextResponse.next();
|
|
98
|
+
}
|
|
99
|
+
// Encode the return URL into the callback so we can redirect after login
|
|
100
|
+
const callbackWithReturn = new URL(casCallbackUrl);
|
|
101
|
+
callbackWithReturn.searchParams.set('returnUrl', returnUrl);
|
|
102
|
+
const params = new URLSearchParams({
|
|
103
|
+
client_id: casClientId,
|
|
104
|
+
response_type: 'token',
|
|
105
|
+
redirect_uri: callbackWithReturn.toString(),
|
|
106
|
+
});
|
|
107
|
+
const casLoginUrl = `${casServerUrl.replace(/\/+$/, '')}/sso/login?${params.toString()}`;
|
|
108
|
+
return NextResponse.redirect(casLoginUrl);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|