@maronn-openid-connect/cli 0.7.1 → 0.8.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/README.md +83 -7
- package/dist/frameworks/hono/index.d.ts.map +1 -1
- package/dist/frameworks/hono/index.js +18 -9
- package/dist/frameworks/hono/index.js.map +1 -1
- package/dist/frameworks/hono/pages.d.ts +7 -6
- package/dist/frameworks/hono/pages.d.ts.map +1 -1
- package/dist/frameworks/hono/pages.js +62 -44
- package/dist/frameworks/hono/pages.js.map +1 -1
- package/dist/frameworks/hono/templates.d.ts +19 -6
- package/dist/frameworks/hono/templates.d.ts.map +1 -1
- package/dist/frameworks/hono/templates.js +284 -304
- package/dist/frameworks/hono/templates.js.map +1 -1
- package/dist/frameworks/hono/views.d.ts +3 -0
- package/dist/frameworks/hono/views.d.ts.map +1 -0
- package/dist/frameworks/hono/views.js +458 -0
- package/dist/frameworks/hono/views.js.map +1 -0
- package/dist/frameworks/nextjs/authorize.d.ts +3 -0
- package/dist/frameworks/nextjs/authorize.d.ts.map +1 -0
- package/dist/frameworks/nextjs/authorize.js +757 -0
- package/dist/frameworks/nextjs/authorize.js.map +1 -0
- package/dist/frameworks/nextjs/ciba.d.ts +8 -0
- package/dist/frameworks/nextjs/ciba.d.ts.map +1 -0
- package/dist/frameworks/nextjs/ciba.js +591 -0
- package/dist/frameworks/nextjs/ciba.js.map +1 -0
- package/dist/frameworks/nextjs/conformance.d.ts +3 -0
- package/dist/frameworks/nextjs/conformance.d.ts.map +1 -0
- package/dist/frameworks/nextjs/conformance.js +2447 -0
- package/dist/frameworks/nextjs/conformance.js.map +1 -0
- package/dist/frameworks/nextjs/device.d.ts +8 -0
- package/dist/frameworks/nextjs/device.d.ts.map +1 -0
- package/dist/frameworks/nextjs/device.js +662 -0
- package/dist/frameworks/nextjs/device.js.map +1 -0
- package/dist/frameworks/nextjs/endpoints.d.ts +7 -0
- package/dist/frameworks/nextjs/endpoints.d.ts.map +1 -0
- package/dist/frameworks/nextjs/endpoints.js +673 -0
- package/dist/frameworks/nextjs/endpoints.js.map +1 -0
- package/dist/frameworks/nextjs/index.d.ts.map +1 -1
- package/dist/frameworks/nextjs/index.js +135 -2
- package/dist/frameworks/nextjs/index.js.map +1 -1
- package/dist/frameworks/nextjs/interaction.d.ts +13 -0
- package/dist/frameworks/nextjs/interaction.d.ts.map +1 -0
- package/dist/frameworks/nextjs/interaction.js +864 -0
- package/dist/frameworks/nextjs/interaction.js.map +1 -0
- package/dist/frameworks/nextjs/logout.d.ts +5 -0
- package/dist/frameworks/nextjs/logout.d.ts.map +1 -0
- package/dist/frameworks/nextjs/logout.js +271 -0
- package/dist/frameworks/nextjs/logout.js.map +1 -0
- package/dist/frameworks/nextjs/metadata.d.ts +6 -0
- package/dist/frameworks/nextjs/metadata.d.ts.map +1 -0
- package/dist/frameworks/nextjs/metadata.js +339 -0
- package/dist/frameworks/nextjs/metadata.js.map +1 -0
- package/dist/frameworks/nextjs/provider.d.ts +6 -0
- package/dist/frameworks/nextjs/provider.d.ts.map +1 -0
- package/dist/frameworks/nextjs/provider.js +797 -0
- package/dist/frameworks/nextjs/provider.js.map +1 -0
- package/dist/frameworks/nextjs/token.d.ts +7 -0
- package/dist/frameworks/nextjs/token.d.ts.map +1 -0
- package/dist/frameworks/nextjs/token.js +1337 -0
- package/dist/frameworks/nextjs/token.js.map +1 -0
- package/dist/frameworks/web-standard/templates.d.ts +1 -13
- package/dist/frameworks/web-standard/templates.d.ts.map +1 -1
- package/dist/frameworks/web-standard/templates.js +12 -1296
- package/dist/frameworks/web-standard/templates.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.js","sourceRoot":"","sources":["../../../src/frameworks/nextjs/interaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function nextJsLogoutConfigTemplate(): string;
|
|
2
|
+
export declare function nextJsLogoutScreensTemplate(): string;
|
|
3
|
+
export declare function nextJsLogoutRouteTemplate(corePkg: string): string;
|
|
4
|
+
export declare function nextJsLogoutApproveRouteTemplate(): string;
|
|
5
|
+
//# sourceMappingURL=logout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../../src/frameworks/nextjs/logout.ts"],"names":[],"mappings":"AAUA,wBAAgB,0BAA0B,IAAI,MAAM,CAenD;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CAuDpD;AAGD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA4IjE;AAGD,wBAAgB,gCAAgC,IAAI,MAAM,CAsDzD"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { EXPERIMENTAL_PACKAGE } from '../hono/templates.js';
|
|
2
|
+
const LOGOUT_PACKAGE = `${EXPERIMENTAL_PACKAGE}/rp-initiated-logout`;
|
|
3
|
+
export function nextJsLogoutConfigTemplate() {
|
|
4
|
+
return `/**
|
|
5
|
+
* EXPERIMENTAL — RP-Initiated Logout settings.
|
|
6
|
+
*
|
|
7
|
+
* postLogoutRedirectUris is the registry §3 checks against: client_id → the
|
|
8
|
+
* exact post_logout_redirect_uri values that client registered (a registry of
|
|
9
|
+
* its own — the authorize redirect_uris are NOT reused). A requested URI is used
|
|
10
|
+
* only on an exact string match for the client the id_token_hint verified for;
|
|
11
|
+
* everything else falls back to the logged-out screen (fail-closed). The
|
|
12
|
+
* default is empty, so no logout redirect happens until you register one here.
|
|
13
|
+
*/
|
|
14
|
+
export const rpInitiatedLogoutConfig = {
|
|
15
|
+
postLogoutRedirectUris: {} as Record<string, string[]>,
|
|
16
|
+
};
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
export function nextJsLogoutScreensTemplate() {
|
|
20
|
+
return `/**
|
|
21
|
+
* EXPERIMENTAL — the RP-Initiated Logout screens (RP-Initiated Logout 1.0 §2).
|
|
22
|
+
*
|
|
23
|
+
* Restyle the UI here. The confirmation form must keep posting csrf_token to
|
|
24
|
+
* /logout/approve: it is paired with the HttpOnly cookie the end_session
|
|
25
|
+
* endpoint mints.
|
|
26
|
+
*/
|
|
27
|
+
import { escapeHtml, htmlResponse } from '../_oidc-provider/html';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The logout confirmation screen (§2 MUST when no valid id_token_hint is
|
|
31
|
+
* presented). The wording is the same on every path into it — no hint, an
|
|
32
|
+
* invalid or expired hint, another user's session, no session at all — so the
|
|
33
|
+
* page is no oracle for session state or for why the hint failed.
|
|
34
|
+
*/
|
|
35
|
+
export function confirmationScreen(csrfToken: string, cookies: readonly string[]): Response {
|
|
36
|
+
return htmlResponse(
|
|
37
|
+
\`<!DOCTYPE html>
|
|
38
|
+
<html>
|
|
39
|
+
<head><title>Log out</title></head>
|
|
40
|
+
<body>
|
|
41
|
+
<h1>Log out</h1>
|
|
42
|
+
<p>Do you want to log out of the OpenID Provider?</p>
|
|
43
|
+
<p>If you did not request this, close this page.</p>
|
|
44
|
+
<form method="POST" action="/logout/approve">
|
|
45
|
+
<input type="hidden" name="csrf_token" value="\${escapeHtml(csrfToken)}" />
|
|
46
|
+
<button type="submit">Log out</button>
|
|
47
|
+
</form>
|
|
48
|
+
</body>
|
|
49
|
+
</html>\`,
|
|
50
|
+
{ cookies },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The logged-out screen. It shows no End-User or client identifier, and its
|
|
56
|
+
* wording never depends on whether anything was actually deleted — varying it
|
|
57
|
+
* would make the page a session-existence oracle.
|
|
58
|
+
*/
|
|
59
|
+
export function completedScreen(cookies: readonly string[]): Response {
|
|
60
|
+
return htmlResponse(
|
|
61
|
+
\`<!DOCTYPE html>
|
|
62
|
+
<html>
|
|
63
|
+
<head><title>Logged out</title></head>
|
|
64
|
+
<body>
|
|
65
|
+
<h1>Logged out</h1>
|
|
66
|
+
<p>You have been logged out.</p>
|
|
67
|
+
<p>You can close this page.</p>
|
|
68
|
+
</body>
|
|
69
|
+
</html>\`,
|
|
70
|
+
{ cookies },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
export function nextJsLogoutRouteTemplate(corePkg) {
|
|
76
|
+
return `/**
|
|
77
|
+
* EXPERIMENTAL — end_session_endpoint (OpenID Connect RP-Initiated Logout 1.0).
|
|
78
|
+
*
|
|
79
|
+
* Generated because the OP was created with \`--enable rp-initiated-logout\`.
|
|
80
|
+
* Backed by ${EXPERIMENTAL_PACKAGE}, whose API is NOT stable: it may change in a
|
|
81
|
+
* breaking way between releases. Do not build production code on it without
|
|
82
|
+
* pinning the version.
|
|
83
|
+
*
|
|
84
|
+
* The RP sends the user agent here (GET or POST, §2 MUST) to end the OP browser
|
|
85
|
+
* session. A request whose id_token_hint verifies against this OP's keys AND
|
|
86
|
+
* matches the current session's End-User logs out immediately; every other
|
|
87
|
+
* request — no hint, an invalid or expired hint, a client_id that mismatches the
|
|
88
|
+
* hint audience, no session, another user's session — falls to one shared
|
|
89
|
+
* confirmation screen (§2 MUST; §7: an unauthenticated logout link would
|
|
90
|
+
* otherwise be a denial-of-service primitive). The failure reason is never
|
|
91
|
+
* disclosed: it would turn this endpoint into an oracle for session state.
|
|
92
|
+
*
|
|
93
|
+
* ## Why the confirmation needs a cookie + token pair
|
|
94
|
+
*
|
|
95
|
+
* Approving the confirmation ends a session, so a forged cross-site POST must
|
|
96
|
+
* not drive it. The confirmation screen hands this browser a fresh secret
|
|
97
|
+
* twice: in an HttpOnly cookie and in the form's hidden csrf_token, and
|
|
98
|
+
* /logout/approve runs only when both come back equal. The cookie also carries
|
|
99
|
+
* the OP-computed post-logout redirect, so the id_token_hint is never
|
|
100
|
+
* round-tripped through the HTML page.
|
|
101
|
+
*/
|
|
102
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
103
|
+
import {
|
|
104
|
+
decideLogoutFlow,
|
|
105
|
+
extractIdTokenHintAudience,
|
|
106
|
+
parseEndSessionRequest,
|
|
107
|
+
resolvePostLogoutRedirect,
|
|
108
|
+
} from '${LOGOUT_PACKAGE}';
|
|
109
|
+
import { IdTokenHintError, generateRandomString, validateIdTokenHint } from '${corePkg}';
|
|
110
|
+
import { readFormFields } from '../_oidc-provider/http';
|
|
111
|
+
import { config, idTokenHintJwks, loadSigningKeys, stores } from '../_oidc-provider/provider';
|
|
112
|
+
import {
|
|
113
|
+
buildClearedSessionCookie,
|
|
114
|
+
buildLogoutConfirmationCookie,
|
|
115
|
+
parseSessionId,
|
|
116
|
+
} from '../_oidc-provider/store';
|
|
117
|
+
import { rpInitiatedLogoutConfig } from './config';
|
|
118
|
+
import { completedScreen, confirmationScreen } from './screens';
|
|
119
|
+
|
|
120
|
+
export const dynamic = 'force-dynamic';
|
|
121
|
+
export const runtime = 'nodejs';
|
|
122
|
+
|
|
123
|
+
export async function GET(request: NextRequest): Promise<Response> {
|
|
124
|
+
return endSession(request, request.nextUrl.searchParams);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** §2: POST carries the same parameters in an application/x-www-form-urlencoded body. */
|
|
128
|
+
export async function POST(request: Request): Promise<Response> {
|
|
129
|
+
const params = new URLSearchParams();
|
|
130
|
+
for (const [name, value] of await readFormFields(request)) {
|
|
131
|
+
if (typeof value === 'string') params.append(name, value);
|
|
132
|
+
}
|
|
133
|
+
return endSession(request, params);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function endSession(request: Request, params: URLSearchParams): Promise<Response> {
|
|
137
|
+
const endSessionRequest = parseEndSessionRequest(params);
|
|
138
|
+
// logout_hint and ui_locales are accepted but unused (OPTIONAL, §2). The hint
|
|
139
|
+
// is never logged — it can identify the End-User — and neither is the
|
|
140
|
+
// id_token_hint value itself.
|
|
141
|
+
|
|
142
|
+
// §2: verify the hint (signature / iss / aud / exp) against the keys
|
|
143
|
+
// id_token_hint is verified with elsewhere. The expected audience is the
|
|
144
|
+
// client_id parameter when present, otherwise it is read — unverified — from
|
|
145
|
+
// the hint payload; trust comes from validateIdTokenHint afterwards.
|
|
146
|
+
let verifiedHint: { sub: string; [key: string]: unknown } | null = null;
|
|
147
|
+
let expectedAudience: string | null = null;
|
|
148
|
+
if (endSessionRequest.idTokenHint !== undefined) {
|
|
149
|
+
expectedAudience =
|
|
150
|
+
endSessionRequest.clientId ?? extractIdTokenHintAudience(endSessionRequest.idTokenHint);
|
|
151
|
+
if (expectedAudience !== null) {
|
|
152
|
+
try {
|
|
153
|
+
verifiedHint = await validateIdTokenHint(endSessionRequest.idTokenHint, {
|
|
154
|
+
expectedIss: config.issuer,
|
|
155
|
+
expectedAud: expectedAudience,
|
|
156
|
+
jwks: await idTokenHintJwks(await loadSigningKeys()),
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
// An expired, tampered or foreign hint just fails to prove logout
|
|
160
|
+
// authority, so the request falls to the confirmation path (§2 MUST)
|
|
161
|
+
// with no reason disclosed. Anything else (the keys failing to load)
|
|
162
|
+
// is rethrown: masking an outage as "invalid hint" would silently turn
|
|
163
|
+
// every logout into a confirmation.
|
|
164
|
+
if (!(error instanceof IdTokenHintError)) throw error;
|
|
165
|
+
verifiedHint = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const sessionId = parseSessionId(request.headers.get('Cookie'));
|
|
171
|
+
const session = sessionId ? await stores.browserSessionStore.get(sessionId) : undefined;
|
|
172
|
+
|
|
173
|
+
const decision = decideLogoutFlow({
|
|
174
|
+
verifiedHint,
|
|
175
|
+
expectedAudience,
|
|
176
|
+
clientIdParam: endSessionRequest.clientId,
|
|
177
|
+
sessionSubject: session ? session.subject : null,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// §3: redirect only to the verified client's exactly-matching registered URI,
|
|
181
|
+
// with state appended. Resolved before the branch because the confirmation
|
|
182
|
+
// flow honors the same result after approval.
|
|
183
|
+
const redirectTo = resolvePostLogoutRedirect({
|
|
184
|
+
postLogoutRedirectUri: endSessionRequest.postLogoutRedirectUri,
|
|
185
|
+
state: endSessionRequest.state,
|
|
186
|
+
verifiedClientId: decision.verifiedClientId,
|
|
187
|
+
registeredUris:
|
|
188
|
+
decision.verifiedClientId === null
|
|
189
|
+
? []
|
|
190
|
+
: rpInitiatedLogoutConfig.postLogoutRedirectUris[decision.verifiedClientId] ?? [],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (decision.requiresConfirmation) {
|
|
194
|
+
// §2 MUST: ask first. Nothing is deleted yet. The minted secret pairs the
|
|
195
|
+
// HttpOnly cookie with the form's hidden csrf_token; the redirect target
|
|
196
|
+
// rides inside the cookie.
|
|
197
|
+
const csrfSecret = generateRandomString(32);
|
|
198
|
+
return confirmationScreen(csrfSecret, [buildLogoutConfirmationCookie({ csrfSecret, redirectTo })]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Immediate logout: a valid hint for the current session's End-User (§2).
|
|
202
|
+
// Delete the store entry and expire the cookie together.
|
|
203
|
+
if (sessionId) {
|
|
204
|
+
await stores.browserSessionStore.delete(sessionId);
|
|
205
|
+
}
|
|
206
|
+
const cookies = [buildClearedSessionCookie()];
|
|
207
|
+
if (redirectTo !== null) {
|
|
208
|
+
const response = NextResponse.redirect(redirectTo, 302);
|
|
209
|
+
for (const cookie of cookies) response.headers.append('Set-Cookie', cookie);
|
|
210
|
+
return response;
|
|
211
|
+
}
|
|
212
|
+
return completedScreen(cookies);
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
export function nextJsLogoutApproveRouteTemplate() {
|
|
217
|
+
return `/**
|
|
218
|
+
* EXPERIMENTAL — RP-Initiated Logout confirmation approve.
|
|
219
|
+
*
|
|
220
|
+
* Runs only for the browser that saw the confirmation screen: the HttpOnly
|
|
221
|
+
* cookie and the hidden csrf_token must present the same secret (neither alone
|
|
222
|
+
* is accepted). On success the session is deleted and the redirect decided when
|
|
223
|
+
* the screen was shown — carried in the cookie, never in the form — is honored
|
|
224
|
+
* (§3).
|
|
225
|
+
*
|
|
226
|
+
* Backed by ${EXPERIMENTAL_PACKAGE}, whose API is NOT stable.
|
|
227
|
+
*/
|
|
228
|
+
import { NextResponse } from 'next/server';
|
|
229
|
+
import { errorPage } from '../../_oidc-provider/html';
|
|
230
|
+
import { readFormFields } from '../../_oidc-provider/http';
|
|
231
|
+
import { stores } from '../../_oidc-provider/provider';
|
|
232
|
+
import {
|
|
233
|
+
buildClearedLogoutConfirmationCookie,
|
|
234
|
+
buildClearedSessionCookie,
|
|
235
|
+
parseLogoutConfirmation,
|
|
236
|
+
parseSessionId,
|
|
237
|
+
} from '../../_oidc-provider/store';
|
|
238
|
+
import { completedScreen } from '../screens';
|
|
239
|
+
|
|
240
|
+
export const dynamic = 'force-dynamic';
|
|
241
|
+
export const runtime = 'nodejs';
|
|
242
|
+
|
|
243
|
+
export async function POST(request: Request): Promise<Response> {
|
|
244
|
+
const csrfToken = String((await readFormFields(request)).get('csrf_token') ?? '');
|
|
245
|
+
const cookieHeader = request.headers.get('Cookie');
|
|
246
|
+
|
|
247
|
+
const confirmation = parseLogoutConfirmation(cookieHeader);
|
|
248
|
+
if (confirmation === null || csrfToken === '' || confirmation.csrfSecret !== csrfToken) {
|
|
249
|
+
// Forged, replayed or expired confirmation: nothing is deleted. This is a
|
|
250
|
+
// browser surface, so the answer is the error page, not OAuth error JSON.
|
|
251
|
+
return errorPage('Invalid logout confirmation', 400);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// The End-User explicitly approved (§2). When the session is already gone
|
|
255
|
+
// there is nothing to delete and the answer is the same either way — the
|
|
256
|
+
// confirmation flow is no oracle for whether a session existed.
|
|
257
|
+
const sessionId = parseSessionId(cookieHeader);
|
|
258
|
+
if (sessionId) {
|
|
259
|
+
await stores.browserSessionStore.delete(sessionId);
|
|
260
|
+
}
|
|
261
|
+
const cookies = [buildClearedSessionCookie(), buildClearedLogoutConfirmationCookie()];
|
|
262
|
+
if (confirmation.redirectTo !== null) {
|
|
263
|
+
const response = NextResponse.redirect(confirmation.redirectTo, 302);
|
|
264
|
+
for (const cookie of cookies) response.headers.append('Set-Cookie', cookie);
|
|
265
|
+
return response;
|
|
266
|
+
}
|
|
267
|
+
return completedScreen(cookies);
|
|
268
|
+
}
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../../src/frameworks/nextjs/logout.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,cAAc,GAAG,GAAG,oBAAoB,sBAAsB,CAAC;AAGrE,MAAM,UAAU,0BAA0B;IACxC,OAAO;;;;;;;;;;;;;CAaR,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,2BAA2B;IACzC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDR,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,yBAAyB,CAAC,OAAe;IACvD,OAAO;;;;eAIM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA4BzB,cAAc;+EACuD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyGrF,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,gCAAgC;IAC9C,OAAO;;;;;;;;;eASM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2ClC,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OidcFeatureConfig } from '../../features.js';
|
|
2
|
+
export declare function nextJsSupportedGrantTypes(features: OidcFeatureConfig): string[];
|
|
3
|
+
export declare function nextJsSupportedScopes(features: OidcFeatureConfig, scopes: string[]): string[];
|
|
4
|
+
export declare function nextJsDiscoveryRouteTemplate(corePkg: string, features?: OidcFeatureConfig, scopes?: string[]): string;
|
|
5
|
+
export declare function nextJsJwksRouteTemplate(corePkg: string): string;
|
|
6
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/frameworks/nextjs/metadata.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAQ3D,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAiB/E;AAMD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAU7F;AAGD,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,iBAAoC,EAC9C,MAAM,GAAE,MAAM,EAAO,GACpB,MAAM,CAmPR;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0E/D"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { DEFAULT_FEATURES } from '../../features.js';
|
|
2
|
+
export function nextJsSupportedGrantTypes(features) {
|
|
3
|
+
return [
|
|
4
|
+
'authorization_code',
|
|
5
|
+
...(features.refreshToken ? ['refresh_token'] : []),
|
|
6
|
+
...(features.tokenExchange || features.idJag
|
|
7
|
+
? ['urn:ietf:params:oauth:grant-type:token-exchange']
|
|
8
|
+
: []),
|
|
9
|
+
...(features.idJag ? ['urn:ietf:params:oauth:grant-type:jwt-bearer'] : []),
|
|
10
|
+
...(features.deviceAuthorizationGrant
|
|
11
|
+
? ['urn:ietf:params:oauth:grant-type:device_code']
|
|
12
|
+
: []),
|
|
13
|
+
...(features.ciba ? ['urn:openid:params:grant-type:ciba'] : []),
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
export function nextJsSupportedScopes(features, scopes) {
|
|
17
|
+
return [
|
|
18
|
+
'openid',
|
|
19
|
+
'profile',
|
|
20
|
+
'email',
|
|
21
|
+
'address',
|
|
22
|
+
'phone',
|
|
23
|
+
...(features.refreshToken ? ['offline_access'] : []),
|
|
24
|
+
...scopes,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
export function nextJsDiscoveryRouteTemplate(corePkg, features = DEFAULT_FEATURES, scopes = []) {
|
|
28
|
+
const customScopesDeclared = scopes.length > 0;
|
|
29
|
+
const customScopeImport = customScopesDeclared
|
|
30
|
+
? `
|
|
31
|
+
import { SUPPORTED_SCOPES } from '../../_oidc-provider/scopes';`
|
|
32
|
+
: '';
|
|
33
|
+
const scopesSupportedEntry = customScopesDeclared
|
|
34
|
+
? ` // OIDC Discovery 1.0 §3: scopes_supported is this OP's scope allow list —
|
|
35
|
+
// the standard scopes plus the custom ones declared with --scope (see
|
|
36
|
+
// scopes.ts). It lists what the provider accepts, not what a particular
|
|
37
|
+
// End-User is granted (resolveGrantableScopes decides that).
|
|
38
|
+
scopesSupported: [...SUPPORTED_SCOPES],
|
|
39
|
+
`
|
|
40
|
+
: features.refreshToken
|
|
41
|
+
? ` // OIDC Core 1.0 §11: offline_access is advertised so relying parties (and the
|
|
42
|
+
// OIDF Conformance Suite's oidcc-refresh-token module) know they may request
|
|
43
|
+
// refresh tokens via 'scope=openid offline_access' with prompt=consent.
|
|
44
|
+
// It is a refresh-token request scope, not a claim scope, so no matching
|
|
45
|
+
// entry is added to claimsSupported.
|
|
46
|
+
scopesSupported: ['openid', 'profile', 'email', 'address', 'phone', 'offline_access'],
|
|
47
|
+
`
|
|
48
|
+
: ` // The refresh_token feature is disabled, so offline_access is not advertised
|
|
49
|
+
// (OIDC Core 1.0 §11: it would never be granted by this provider).
|
|
50
|
+
scopesSupported: ['openid', 'profile', 'email', 'address', 'phone'],
|
|
51
|
+
`;
|
|
52
|
+
const supportedGrantTypes = nextJsSupportedGrantTypes(features).map((grantType) => `'${grantType}'`);
|
|
53
|
+
const requestObjectMetadata = features.requestObject
|
|
54
|
+
? ` // OIDC Core 1.0 §6.1 / OIDC Discovery 1.0 §3: a signed Request Object by value
|
|
55
|
+
// is supported (verified against the client's registered JWKS). request_uri
|
|
56
|
+
// (§6.2) is not, so it is advertised as false explicitly — Discovery defaults
|
|
57
|
+
// request_uri_parameter_supported to true when omitted. 'none' is added only
|
|
58
|
+
// when unsigned objects are accepted for Basic OP conformance compatibility.
|
|
59
|
+
requestParameterSupported: true,
|
|
60
|
+
requestUriParameterSupported: false,
|
|
61
|
+
requestObjectSigningAlgValuesSupported: config.allowUnsignedRequestObject
|
|
62
|
+
? ['RS256', 'none']
|
|
63
|
+
: ['RS256'],
|
|
64
|
+
`
|
|
65
|
+
: ` // OIDC Core 1.0 §6.3: the request parameter (Request Object) is disabled in
|
|
66
|
+
// this generated provider, so request_parameter_supported is advertised as
|
|
67
|
+
// false. request_uri (§6.2) remains unsupported as well.
|
|
68
|
+
requestParameterSupported: false,
|
|
69
|
+
requestUriParameterSupported: false,
|
|
70
|
+
`;
|
|
71
|
+
const rfc8414Comment = features.introspection && features.revocation
|
|
72
|
+
? ` // RFC 8414 — both endpoints require client authentication.
|
|
73
|
+
`
|
|
74
|
+
: features.introspection || features.revocation
|
|
75
|
+
? ` // RFC 8414 — the endpoint requires client authentication.
|
|
76
|
+
`
|
|
77
|
+
: '';
|
|
78
|
+
const introspectionMetadata = features.introspection
|
|
79
|
+
? ` introspectionEndpoint: \`\${issuer}/introspect\`,
|
|
80
|
+
introspectionEndpointAuthMethodsSupported: [
|
|
81
|
+
'client_secret_basic',
|
|
82
|
+
'client_secret_post',
|
|
83
|
+
],
|
|
84
|
+
`
|
|
85
|
+
: '';
|
|
86
|
+
const revocationMetadata = features.revocation
|
|
87
|
+
? ` revocationEndpoint: \`\${issuer}/revoke\`,
|
|
88
|
+
revocationEndpointAuthMethodsSupported: [
|
|
89
|
+
'client_secret_basic',
|
|
90
|
+
'client_secret_post',
|
|
91
|
+
],
|
|
92
|
+
`
|
|
93
|
+
: '';
|
|
94
|
+
const responseModesSupportedEntry = features.jarm
|
|
95
|
+
? ` // OAuth 2.0 Multiple Response Type Encoding Practices §2 / OIDC Discovery 1.0
|
|
96
|
+
// §3: the code flow answers via query. EXPERIMENTAL (JARM §4): this provider
|
|
97
|
+
// was generated with --enable jarm, so the JWT-secured query modes are
|
|
98
|
+
// advertised alongside it.
|
|
99
|
+
responseModesSupported: ['query', 'query.jwt', 'jwt'],`
|
|
100
|
+
: ` // OAuth 2.0 Multiple Response Type Encoding Practices §2 / OIDC Discovery 1.0
|
|
101
|
+
// §3: the OP only implements the authorization code flow, whose response is
|
|
102
|
+
// returned via query, so response_modes_supported is pinned to ['query'].
|
|
103
|
+
responseModesSupported: ['query'],`;
|
|
104
|
+
const parImport = features.par
|
|
105
|
+
? `
|
|
106
|
+
import { parConfig } from '../../par/config';`
|
|
107
|
+
: '';
|
|
108
|
+
const extensionMetadata = [
|
|
109
|
+
...(features.par
|
|
110
|
+
? [` // EXPERIMENTAL — RFC 9126 §5. require_pushed_authorization_requests is only
|
|
111
|
+
// advertised when PAR is actually enforced (its default is false).
|
|
112
|
+
pushed_authorization_request_endpoint: \`\${issuer}/par\`,
|
|
113
|
+
...(parConfig.requirePushedAuthorizationRequests
|
|
114
|
+
? { require_pushed_authorization_requests: true }
|
|
115
|
+
: {}),`]
|
|
116
|
+
: []),
|
|
117
|
+
...(features.deviceAuthorizationGrant
|
|
118
|
+
? [` // EXPERIMENTAL — RFC 8628 §4.
|
|
119
|
+
device_authorization_endpoint: \`\${issuer}/device_authorization\`,`]
|
|
120
|
+
: []),
|
|
121
|
+
...(features.ciba
|
|
122
|
+
? [` // EXPERIMENTAL — CIBA Core 1.0 §4. Only the poll delivery mode is offered.
|
|
123
|
+
backchannel_token_delivery_modes_supported: ['poll'],
|
|
124
|
+
backchannel_authentication_endpoint: \`\${issuer}/backchannel_authentication\`,`]
|
|
125
|
+
: []),
|
|
126
|
+
...(features.jarm
|
|
127
|
+
? [` // EXPERIMENTAL — JARM §4. The response JWT is always signed with RS256 (JARM
|
|
128
|
+
// §3: the default for a client that registered no
|
|
129
|
+
// authorization_signed_response_alg).
|
|
130
|
+
authorization_signing_alg_values_supported: ['RS256'],`]
|
|
131
|
+
: []),
|
|
132
|
+
...(features.idJag
|
|
133
|
+
? [` // EXPERIMENTAL — ID-JAG draft §7.1 / §7.2: this OP issues ID-JAGs via token
|
|
134
|
+
// exchange and redeems them on the jwt-bearer grant. Which issuers and
|
|
135
|
+
// audiences are trusted is local policy and is not disclosed (draft §9.4).
|
|
136
|
+
identity_chaining_requested_token_types_supported: ['urn:ietf:params:oauth:token-type:id-jag'],
|
|
137
|
+
authorization_grant_profiles_supported: ['urn:ietf:params:oauth:grant-profile:id-jag'],`]
|
|
138
|
+
: []),
|
|
139
|
+
...(features.introspection && features.jwtIntrospectionResponse
|
|
140
|
+
? [` // EXPERIMENTAL — RFC 9701 §7. The introspection response JWT is always signed
|
|
141
|
+
// with RS256 (§6: the default for a client that registered no
|
|
142
|
+
// introspection_signed_response_alg).
|
|
143
|
+
introspection_signing_alg_values_supported: ['RS256'],`]
|
|
144
|
+
: []),
|
|
145
|
+
...(features.rpInitiatedLogout
|
|
146
|
+
? [` // EXPERIMENTAL — RP-Initiated Logout 1.0 §2.1.
|
|
147
|
+
end_session_endpoint: \`\${issuer}/logout\`,`]
|
|
148
|
+
: []),
|
|
149
|
+
];
|
|
150
|
+
const extensionMetadataBlock = extensionMetadata.length > 0
|
|
151
|
+
? `\n${extensionMetadata.join('\n')}`
|
|
152
|
+
: '';
|
|
153
|
+
return `/**
|
|
154
|
+
* OpenID Provider Configuration (OIDC Discovery 1.0 §4).
|
|
155
|
+
*/
|
|
156
|
+
import { buildProviderMetadata, getJwaAlgorithm } from '${corePkg}';
|
|
157
|
+
import { config, loadSigningKeys } from '../../_oidc-provider/provider';
|
|
158
|
+
import {
|
|
159
|
+
corsPreflight,
|
|
160
|
+
publicCors,
|
|
161
|
+
signingKeysUnavailable,
|
|
162
|
+
withCors,
|
|
163
|
+
} from '../../_oidc-provider/http';${parImport}${customScopeImport}
|
|
164
|
+
|
|
165
|
+
export const dynamic = 'force-dynamic';
|
|
166
|
+
export const runtime = 'nodejs';
|
|
167
|
+
|
|
168
|
+
export async function GET(request: Request): Promise<Response> {
|
|
169
|
+
return withCors(request, publicCors, await providerMetadata());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function OPTIONS(request: Request): Response {
|
|
173
|
+
return corsPreflight(request, publicCors);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function providerMetadata(): Promise<Response> {
|
|
177
|
+
const keys = await loadSigningKeys().catch(() => null);
|
|
178
|
+
if (!keys) return signingKeysUnavailable();
|
|
179
|
+
|
|
180
|
+
const issuer = config.issuer;
|
|
181
|
+
const metadata = buildProviderMetadata({
|
|
182
|
+
issuer,
|
|
183
|
+
authorizationEndpoint: \`\${issuer}/authorize\`,
|
|
184
|
+
tokenEndpoint: \`\${issuer}/token\`,
|
|
185
|
+
jwksUri: \`\${issuer}/.well-known/jwks.json\`,
|
|
186
|
+
responseTypesSupported: ['code'],
|
|
187
|
+
${responseModesSupportedEntry}
|
|
188
|
+
subjectTypesSupported: ['public'],
|
|
189
|
+
// OIDC Core 1.0 §15.1: id_token_signing_alg_values_supported is derived from
|
|
190
|
+
// every registered ID Token key, so a mixed RS256 + ES256 set is advertised
|
|
191
|
+
// as such (buildProviderMetadata enforces that RS256 is present).
|
|
192
|
+
idTokenSigningKeys: keys.idToken.registered.map((key) => key.privateKey),
|
|
193
|
+
userinfoEndpoint: \`\${issuer}/userinfo\`,
|
|
194
|
+
${scopesSupportedEntry} // OIDC Discovery 1.0 §3 / Core 1.0 §5.6: this OP produces Normal Claims only
|
|
195
|
+
// (no _claim_names / _claim_sources).
|
|
196
|
+
claimTypesSupported: ['normal'],
|
|
197
|
+
claimsSupported: [
|
|
198
|
+
'sub',
|
|
199
|
+
'iss',
|
|
200
|
+
'aud',
|
|
201
|
+
'exp',
|
|
202
|
+
'iat',
|
|
203
|
+
// OIDC Core 1.0 §2 / §3.1.3.6: ID Token protocol claims the OP issues.
|
|
204
|
+
// c_hash is omitted on purpose: the Hybrid flow is not implemented.
|
|
205
|
+
'auth_time',
|
|
206
|
+
'nonce',
|
|
207
|
+
'acr',
|
|
208
|
+
'amr',
|
|
209
|
+
'azp',
|
|
210
|
+
'at_hash',
|
|
211
|
+
'name',
|
|
212
|
+
'family_name',
|
|
213
|
+
'given_name',
|
|
214
|
+
'middle_name',
|
|
215
|
+
'nickname',
|
|
216
|
+
'preferred_username',
|
|
217
|
+
'profile',
|
|
218
|
+
'picture',
|
|
219
|
+
'website',
|
|
220
|
+
'gender',
|
|
221
|
+
'birthdate',
|
|
222
|
+
'zoneinfo',
|
|
223
|
+
'locale',
|
|
224
|
+
'updated_at',
|
|
225
|
+
'email',
|
|
226
|
+
'email_verified',
|
|
227
|
+
'address',
|
|
228
|
+
'phone_number',
|
|
229
|
+
'phone_number_verified',
|
|
230
|
+
],
|
|
231
|
+
grantTypesSupported: [${supportedGrantTypes.join(', ')}],
|
|
232
|
+
// RFC 6749 §2.1 / OAuth 2.1 §2.4: 'none' advertises that public clients
|
|
233
|
+
// (no client_secret) are accepted at the token endpoint.
|
|
234
|
+
tokenEndpointAuthMethodsSupported: [
|
|
235
|
+
'client_secret_basic',
|
|
236
|
+
'client_secret_post',
|
|
237
|
+
'none',
|
|
238
|
+
],
|
|
239
|
+
// OIDC Core 1.0 §5.3.2: the algs the registered UserInfo keys can sign with,
|
|
240
|
+
// so clients relying on userinfo_signed_response_alg can trust the metadata.
|
|
241
|
+
userinfoSigningAlgValuesSupported: [
|
|
242
|
+
...new Set(keys.userinfo.registered.map((key) => getJwaAlgorithm(key.privateKey))),
|
|
243
|
+
],
|
|
244
|
+
${requestObjectMetadata} // OIDC Discovery 1.0 §3 / Core 1.0 §5.5: the claims request parameter is
|
|
245
|
+
// implemented for both the ID Token and UserInfo.
|
|
246
|
+
claimsParameterSupported: true,
|
|
247
|
+
// RFC 9207 §3: every authorization response carries iss.
|
|
248
|
+
authorizationResponseIssParameterSupported: true,
|
|
249
|
+
${rfc8414Comment}${introspectionMetadata}${revocationMetadata} });
|
|
250
|
+
|
|
251
|
+
// RFC 8414 §3.2 / RFC 9111 §5.2: discovery metadata is cacheable; 3600s, the
|
|
252
|
+
// same freshness lifetime as the JWKS endpoint.
|
|
253
|
+
return Response.json(
|
|
254
|
+
{
|
|
255
|
+
...metadata,
|
|
256
|
+
// RFC 7636 / OAuth 2.1: not an OIDC Discovery field, so added here.
|
|
257
|
+
code_challenge_methods_supported: ['S256'],${extensionMetadataBlock}
|
|
258
|
+
},
|
|
259
|
+
{ headers: { 'Cache-Control': 'public, max-age=3600' } },
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
export function nextJsJwksRouteTemplate(corePkg) {
|
|
265
|
+
return `/**
|
|
266
|
+
* JWKS endpoint: the public keys every OP-signed token is verified with.
|
|
267
|
+
*
|
|
268
|
+
* All three key sets are published (general, ID Token, UserInfo) including
|
|
269
|
+
* rotated-out keys, so tokens signed before a rotation keep verifying until
|
|
270
|
+
* they expire. A kid appears once; of the keys without a kid only the most
|
|
271
|
+
* recently added one is published.
|
|
272
|
+
*/
|
|
273
|
+
import { exportJwks, extractAlgorithmParamsFromJwk, type SigningKey } from '${corePkg}';
|
|
274
|
+
import { loadSigningKeys } from '../../_oidc-provider/provider';
|
|
275
|
+
import {
|
|
276
|
+
corsPreflight,
|
|
277
|
+
publicCors,
|
|
278
|
+
signingKeysUnavailable,
|
|
279
|
+
withCors,
|
|
280
|
+
} from '../../_oidc-provider/http';
|
|
281
|
+
|
|
282
|
+
export const dynamic = 'force-dynamic';
|
|
283
|
+
export const runtime = 'nodejs';
|
|
284
|
+
|
|
285
|
+
export async function GET(request: Request): Promise<Response> {
|
|
286
|
+
return withCors(request, publicCors, await jwks());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function OPTIONS(request: Request): Response {
|
|
290
|
+
return corsPreflight(request, publicCors);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function jwks(): Promise<Response> {
|
|
294
|
+
const keys = await loadSigningKeys().catch(() => null);
|
|
295
|
+
if (!keys) return signingKeysUnavailable();
|
|
296
|
+
|
|
297
|
+
const published = publishedKeys([
|
|
298
|
+
...keys.general.registered,
|
|
299
|
+
...keys.idToken.registered,
|
|
300
|
+
...keys.userinfo.registered,
|
|
301
|
+
]);
|
|
302
|
+
const entries = await Promise.all(
|
|
303
|
+
published.map(async (key) => ({
|
|
304
|
+
publicKey: await crypto.subtle.importKey(
|
|
305
|
+
'jwk',
|
|
306
|
+
key.publicJwk,
|
|
307
|
+
extractAlgorithmParamsFromJwk(key.publicJwk),
|
|
308
|
+
true,
|
|
309
|
+
['verify'],
|
|
310
|
+
),
|
|
311
|
+
keyId: key.keyId,
|
|
312
|
+
})),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return Response.json(await exportJwks(entries), {
|
|
316
|
+
headers: { 'Cache-Control': 'public, max-age=3600' },
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The first key of every kid, plus the last key that has no kid (the most
|
|
322
|
+
* recent one wins, since rotation appends).
|
|
323
|
+
*/
|
|
324
|
+
function publishedKeys(candidates: readonly SigningKey[]): SigningKey[] {
|
|
325
|
+
let lastWithoutKid = -1;
|
|
326
|
+
candidates.forEach((key, index) => {
|
|
327
|
+
if (key.keyId === undefined) lastWithoutKid = index;
|
|
328
|
+
});
|
|
329
|
+
const seenKids = new Set<string>();
|
|
330
|
+
return candidates.filter((key, index) => {
|
|
331
|
+
if (key.keyId === undefined) return index === lastWithoutKid;
|
|
332
|
+
if (seenKids.has(key.keyId)) return false;
|
|
333
|
+
seenKids.add(key.keyId);
|
|
334
|
+
return true;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../src/frameworks/nextjs/metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OidcFeatureConfig } from '../../features.js';
|
|
2
|
+
export declare function nextJsProviderTemplate(corePkg: string, features?: OidcFeatureConfig): string;
|
|
3
|
+
export declare function nextJsStorageBackendTemplate(): string;
|
|
4
|
+
export declare function nextJsHttpTemplate(): string;
|
|
5
|
+
export declare function nextJsHtmlTemplate(): string;
|
|
6
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/frameworks/nextjs/provider.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO3D,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,iBAAoC,GAC7C,MAAM,CA6XR;AAMD,wBAAgB,4BAA4B,IAAI,MAAM,CA2LrD;AAQD,wBAAgB,kBAAkB,IAAI,MAAM,CA+K3C;AAOD,wBAAgB,kBAAkB,IAAI,MAAM,CAwD3C"}
|