@maronn-openid-connect/cli 0.7.0 → 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 +439 -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":"authorize.js","sourceRoot":"","sources":["../../../src/frameworks/nextjs/authorize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OidcFeatureConfig } from '../../features.js';
|
|
2
|
+
export declare function nextJsCibaConfigTemplate(): string;
|
|
3
|
+
export declare function nextJsBackchannelAuthenticationRouteTemplate(corePkg: string, features?: OidcFeatureConfig, scopes?: string[]): string;
|
|
4
|
+
export declare function nextJsCibaScreensTemplate(scopes?: string[]): string;
|
|
5
|
+
export declare function nextJsCibaRouteTemplate(): string;
|
|
6
|
+
export declare function nextJsCibaLoginRouteTemplate(corePkg: string): string;
|
|
7
|
+
export declare function nextJsCibaApproveRouteTemplate(corePkg: string, scopes?: string[]): string;
|
|
8
|
+
//# sourceMappingURL=ciba.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ciba.d.ts","sourceRoot":"","sources":["../../../src/frameworks/nextjs/ciba.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,wBAAgB,wBAAwB,IAAI,MAAM,CAqCjD;AAGD,wBAAgB,4CAA4C,CAC1D,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,iBAAoC,EAC9C,MAAM,GAAE,MAAM,EAAO,GACpB,MAAM,CAyJR;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,GAAE,MAAM,EAAO,GAAG,MAAM,CAoKvE;AAGD,wBAAgB,uBAAuB,IAAI,MAAM,CA0ChD;AAGD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAuFpE;AAGD,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAO,GAAG,MAAM,CAkG7F"}
|
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { DEFAULT_FEATURES } from '../../features.js';
|
|
2
|
+
import { EXPERIMENTAL_PACKAGE } from '../hono/templates.js';
|
|
3
|
+
const CIBA_PACKAGE = `${EXPERIMENTAL_PACKAGE}/ciba`;
|
|
4
|
+
export function nextJsCibaConfigTemplate() {
|
|
5
|
+
return `/**
|
|
6
|
+
* EXPERIMENTAL — CIBA settings (CIBA Core 1.0).
|
|
7
|
+
*
|
|
8
|
+
* Read by the backchannel authentication endpoint and the authentication device
|
|
9
|
+
* UI (/ciba).
|
|
10
|
+
*
|
|
11
|
+
* - authReqIdExpiresIn: §7.3 expires_in, in seconds (range 30–600). Keep it
|
|
12
|
+
* short: it is the window the user has to approve, and the window in which a
|
|
13
|
+
* pending request can pile up on the approval screen.
|
|
14
|
+
* - pollingInterval: §7.3 interval, in seconds (range 1–60). The token endpoint
|
|
15
|
+
* raises a record's own interval by 5 every time it answers slow_down (§11).
|
|
16
|
+
* - maxPendingPerSubject: pending backchannel requests allowed per user (range
|
|
17
|
+
* 1–100) before new ones are refused — the flood defense for the approval
|
|
18
|
+
* screen (the role §7.1.2's unsupported user_code would otherwise play).
|
|
19
|
+
* - maxLoginAttempts: failed /ciba logins allowed per login transaction before
|
|
20
|
+
* it is discarded. Per-transaction only — see ciba/login/route.ts.
|
|
21
|
+
*/
|
|
22
|
+
export const cibaConfig = {
|
|
23
|
+
authReqIdExpiresIn: 120,
|
|
24
|
+
pollingInterval: 5,
|
|
25
|
+
maxPendingPerSubject: 10,
|
|
26
|
+
maxLoginAttempts: 5,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Fail fast on a config edit that leaves the documented ranges: a typo here
|
|
30
|
+
// weakens either the approval-screen flood cap or the polling contract.
|
|
31
|
+
if (cibaConfig.authReqIdExpiresIn < 30 || cibaConfig.authReqIdExpiresIn > 600) {
|
|
32
|
+
throw new Error('cibaConfig.authReqIdExpiresIn must be between 30 and 600 seconds');
|
|
33
|
+
}
|
|
34
|
+
if (cibaConfig.pollingInterval < 1 || cibaConfig.pollingInterval > 60) {
|
|
35
|
+
throw new Error('cibaConfig.pollingInterval must be between 1 and 60 seconds');
|
|
36
|
+
}
|
|
37
|
+
if (cibaConfig.maxPendingPerSubject < 1 || cibaConfig.maxPendingPerSubject > 100) {
|
|
38
|
+
throw new Error('cibaConfig.maxPendingPerSubject must be between 1 and 100');
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
export function nextJsBackchannelAuthenticationRouteTemplate(corePkg, features = DEFAULT_FEATURES, scopes = []) {
|
|
43
|
+
const customScopesDeclared = scopes.length > 0;
|
|
44
|
+
const customScopeImport = customScopesDeclared
|
|
45
|
+
? `
|
|
46
|
+
import { findUnsupportedScopes } from '../_oidc-provider/scopes';`
|
|
47
|
+
: '';
|
|
48
|
+
const customScopeStep = customScopesDeclared
|
|
49
|
+
? ` // RFC 6749 §3.3: reject a scope this OP never declared, the same allow list
|
|
50
|
+
// /authorize uses (scopes.ts). Checked before the pipeline so a request for
|
|
51
|
+
// an unknown scope never reaches the store. offline_access is left out: the
|
|
52
|
+
// pipeline applies its own policy and ignores it when it cannot be granted
|
|
53
|
+
// (OIDC Core 1.0 §11), which must not become an error.
|
|
54
|
+
const unsupportedScopes = findUnsupportedScopes(
|
|
55
|
+
(params['scope'] ?? '')
|
|
56
|
+
.split(' ')
|
|
57
|
+
.filter((scope) => scope.length > 0 && scope !== 'offline_access'),
|
|
58
|
+
);
|
|
59
|
+
if (unsupportedScopes.length > 0) {
|
|
60
|
+
throw new BackchannelAuthenticationError(
|
|
61
|
+
'invalid_scope',
|
|
62
|
+
'Unsupported scope: ' + unsupportedScopes.join(' '),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
`
|
|
67
|
+
: '';
|
|
68
|
+
return `/**
|
|
69
|
+
* EXPERIMENTAL — Backchannel Authentication Endpoint (CIBA Core 1.0, poll mode).
|
|
70
|
+
*
|
|
71
|
+
* Generated because the OP was created with \`--enable ciba\`. Backed by
|
|
72
|
+
* ${EXPERIMENTAL_PACKAGE}, whose API is NOT stable: it may change in a breaking
|
|
73
|
+
* way between releases. Do not build production code on it without pinning the
|
|
74
|
+
* version.
|
|
75
|
+
*
|
|
76
|
+
* The consumption device (a call-center console, a kiosk, a smart speaker
|
|
77
|
+
* backend) POSTs here — back channel, client-authenticated — with a login_hint
|
|
78
|
+
* naming the user, and receives an auth_req_id it polls the token endpoint
|
|
79
|
+
* with. The user approves or denies on their own browser at /ciba.
|
|
80
|
+
*
|
|
81
|
+
* NOTE (CIBA §15): the login_hint is a user identifier and therefore PII. Never
|
|
82
|
+
* log it, and never echo it in an error_description. Rate limiting is left to
|
|
83
|
+
* the deployment layer (reverse proxy / platform); the in-band defenses are
|
|
84
|
+
* mandatory client authentication, the fixed unknown_user_id wording, and the
|
|
85
|
+
* per-subject pending-request cap (config.ts).
|
|
86
|
+
*/
|
|
87
|
+
import {
|
|
88
|
+
BackchannelAuthenticationError,
|
|
89
|
+
processBackchannelAuthenticationRequest,
|
|
90
|
+
type CibaClientInfo,
|
|
91
|
+
} from '${CIBA_PACKAGE}';
|
|
92
|
+
import {
|
|
93
|
+
TokenError,
|
|
94
|
+
extractClientCredentials,
|
|
95
|
+
resolveAuthenticatedTokenClient,
|
|
96
|
+
sanitizeErrorDescription,
|
|
97
|
+
validateClientAuthMethod,
|
|
98
|
+
verifyClientSecret,
|
|
99
|
+
} from '${corePkg}';
|
|
100
|
+
import {
|
|
101
|
+
cibaAuthenticationRequestStore,
|
|
102
|
+
clientResolver,
|
|
103
|
+
resolveCibaUser,
|
|
104
|
+
} from '../_oidc-provider/provider';
|
|
105
|
+
import {
|
|
106
|
+
clientCors,
|
|
107
|
+
corsPreflight,
|
|
108
|
+
isFormUrlEncoded,
|
|
109
|
+
noStoreJson,
|
|
110
|
+
oauthError,
|
|
111
|
+
uniqueParams,
|
|
112
|
+
withCors,
|
|
113
|
+
} from '../_oidc-provider/http';${customScopeImport}
|
|
114
|
+
import { cibaConfig } from './config';
|
|
115
|
+
|
|
116
|
+
export const dynamic = 'force-dynamic';
|
|
117
|
+
export const runtime = 'nodejs';
|
|
118
|
+
|
|
119
|
+
export async function POST(request: Request): Promise<Response> {
|
|
120
|
+
return withCors(request, clientCors, await backchannelAuthentication(request));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function OPTIONS(request: Request): Response {
|
|
124
|
+
return corsPreflight(request, clientCors);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* auth_req_id is a credential, so every answer follows the token response rules
|
|
129
|
+
* of RFC 6749 §5.1 / §5.2 (no-store).
|
|
130
|
+
*/
|
|
131
|
+
async function backchannelAuthentication(request: Request): Promise<Response> {
|
|
132
|
+
// CIBA §7.1: the body MUST be application/x-www-form-urlencoded.
|
|
133
|
+
if (!isFormUrlEncoded(request)) {
|
|
134
|
+
return oauthError('invalid_request', 'Backchannel authentication requests must use application/x-www-form-urlencoded');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// RFC 6749 §3.1: request parameters MUST NOT be repeated.
|
|
138
|
+
const { params, duplicateKey } = uniqueParams(new URLSearchParams(await request.text()));
|
|
139
|
+
if (duplicateKey !== undefined) {
|
|
140
|
+
return oauthError('invalid_request', \`Parameter "\${sanitizeErrorDescription(duplicateKey)}" must not be repeated\`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// --- Client authentication pipeline -------------------------------------
|
|
145
|
+
// CIBA §7.1: "The Client MUST authenticate ... using the authentication
|
|
146
|
+
// method registered for its client_id" — the same pipeline the token
|
|
147
|
+
// endpoint runs.
|
|
148
|
+
const presentedCredentials = extractClientCredentials({
|
|
149
|
+
params,
|
|
150
|
+
authorizationHeader: request.headers.get('Authorization') ?? '',
|
|
151
|
+
});
|
|
152
|
+
const client = await resolveAuthenticatedTokenClient(
|
|
153
|
+
presentedCredentials.clientId,
|
|
154
|
+
clientResolver,
|
|
155
|
+
);
|
|
156
|
+
validateClientAuthMethod(client, presentedCredentials);
|
|
157
|
+
await verifyClientSecret(client, presentedCredentials.clientSecret);
|
|
158
|
+
|
|
159
|
+
${customScopeStep} // --- Backchannel authentication pipeline --------------------------------
|
|
160
|
+
// Validation runs in CIBA §7.1 order inside the experimental package:
|
|
161
|
+
// client checks (public client / grant registration / delivery mode) →
|
|
162
|
+
// request parameter rejection → the one-and-only-one hint rule → scope →
|
|
163
|
+
// binding_message → requested_expiry → login_hint resolution (resolveCibaUser
|
|
164
|
+
// in provider.ts) → the per-subject pending cap → record creation.
|
|
165
|
+
const response = await processBackchannelAuthenticationRequest({
|
|
166
|
+
params,
|
|
167
|
+
client: client as CibaClientInfo,
|
|
168
|
+
store: cibaAuthenticationRequestStore,
|
|
169
|
+
config: cibaConfig,
|
|
170
|
+
refreshTokenFeatureEnabled: ${features.refreshToken ? 'true' : 'false'},
|
|
171
|
+
resolveUser: resolveCibaUser,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Never log auth_req_id (a live credential) or login_hint (PII, CIBA §15).
|
|
175
|
+
|
|
176
|
+
return noStoreJson(response);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (error instanceof BackchannelAuthenticationError) {
|
|
179
|
+
// CIBA §13 / RFC 6749 §5.2 shape. Authentication failures are core
|
|
180
|
+
// TokenErrors (401).
|
|
181
|
+
return oauthError(error.code, error.errorDescription, error.statusCode);
|
|
182
|
+
}
|
|
183
|
+
if (error instanceof TokenError) {
|
|
184
|
+
return oauthError(
|
|
185
|
+
error.error,
|
|
186
|
+
error.errorDescription,
|
|
187
|
+
error.statusCode,
|
|
188
|
+
error.wwwAuthenticate ? { 'WWW-Authenticate': error.wwwAuthenticate } : undefined,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return oauthError('server_error', undefined, 500);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
export function nextJsCibaScreensTemplate(scopes = []) {
|
|
197
|
+
const customScopesDeclared = scopes.length > 0;
|
|
198
|
+
const customScopeImport = customScopesDeclared
|
|
199
|
+
? `
|
|
200
|
+
import { resolveGrantableScopes } from '../_oidc-provider/scopes';`
|
|
201
|
+
: '';
|
|
202
|
+
const requestRows = customScopesDeclared
|
|
203
|
+
? ` const requests = await Promise.all(
|
|
204
|
+
pending.map(async (record) => ({
|
|
205
|
+
...record,
|
|
206
|
+
// Show only what this End-User can actually grant (scopes.ts).
|
|
207
|
+
scope: await resolveGrantableScopes(record.scope, subject),
|
|
208
|
+
})),
|
|
209
|
+
);`
|
|
210
|
+
: ` const requests = pending;`;
|
|
211
|
+
return `/**
|
|
212
|
+
* EXPERIMENTAL — the screens of the CIBA authentication device UI
|
|
213
|
+
* (CIBA Core 1.0 §7.1).
|
|
214
|
+
*
|
|
215
|
+
* Restyle the UI here. Keep the form actions, field names and decision values:
|
|
216
|
+
* the Route Handlers next to this file read exactly those.
|
|
217
|
+
*/
|
|
218
|
+
import { CibaVerificationError, listPendingCibaRequests } from '${CIBA_PACKAGE}';
|
|
219
|
+
import { cibaAuthenticationRequestStore } from '../_oidc-provider/provider';
|
|
220
|
+
import { errorPage, escapeHtml, htmlResponse } from '../_oidc-provider/html';${customScopeImport}
|
|
221
|
+
|
|
222
|
+
/** The sign-in form; cookies carry the login transaction's browser binding. */
|
|
223
|
+
export function loginScreen(
|
|
224
|
+
params: { loginTransactionId: string; csrfToken: string; error?: string; remainingAttempts?: number },
|
|
225
|
+
cookies: readonly string[] = [],
|
|
226
|
+
): Response {
|
|
227
|
+
const errorHtml = params.error
|
|
228
|
+
? \`<p style="color: red;">\${escapeHtml(params.error)}\${
|
|
229
|
+
params.remainingAttempts !== undefined
|
|
230
|
+
? \`. Attempts remaining: \${params.remainingAttempts}\`
|
|
231
|
+
: ''
|
|
232
|
+
}</p>\`
|
|
233
|
+
: '';
|
|
234
|
+
return htmlResponse(
|
|
235
|
+
\`<!DOCTYPE html>
|
|
236
|
+
<html>
|
|
237
|
+
<head><title>Sign in</title></head>
|
|
238
|
+
<body>
|
|
239
|
+
<h1>Sign in</h1>
|
|
240
|
+
<p>Sign in to review sign-in requests sent to you.</p>
|
|
241
|
+
\${errorHtml}
|
|
242
|
+
<form method="POST" action="/ciba/login">
|
|
243
|
+
<input type="hidden" name="login_transaction_id" value="\${escapeHtml(params.loginTransactionId)}" />
|
|
244
|
+
<input type="hidden" name="csrf_token" value="\${escapeHtml(params.csrfToken)}" />
|
|
245
|
+
<div>
|
|
246
|
+
<label for="username">Username:</label>
|
|
247
|
+
<input type="text" id="username" name="username" required />
|
|
248
|
+
</div>
|
|
249
|
+
<div>
|
|
250
|
+
<label for="password">Password:</label>
|
|
251
|
+
<input type="password" id="password" name="password" required />
|
|
252
|
+
</div>
|
|
253
|
+
<button type="submit">Login</button>
|
|
254
|
+
</form>
|
|
255
|
+
</body>
|
|
256
|
+
</html>\`,
|
|
257
|
+
{ cookies },
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The pending requests addressed to subject, each with a freshly rotated CSRF
|
|
263
|
+
* token — the only place those tokens are ever shown, and it is session-gated.
|
|
264
|
+
*
|
|
265
|
+
* CIBA Core 1.0 §7.1: the binding_message is repeated so the user can check it
|
|
266
|
+
* against the device that started the request before approving. It is
|
|
267
|
+
* client-supplied text, so it is escaped like everything else. Deny is shown
|
|
268
|
+
* with the same prominence as Approve.
|
|
269
|
+
*/
|
|
270
|
+
export async function pendingRequestsScreen(
|
|
271
|
+
subject: string,
|
|
272
|
+
cookies: readonly string[] = [],
|
|
273
|
+
): Promise<Response> {
|
|
274
|
+
const pending = await listPendingCibaRequests({ subject, store: cibaAuthenticationRequestStore });
|
|
275
|
+
${requestRows}
|
|
276
|
+
if (requests.length === 0) {
|
|
277
|
+
return htmlResponse(
|
|
278
|
+
\`<!DOCTYPE html>
|
|
279
|
+
<html>
|
|
280
|
+
<head><title>Sign-in Requests</title></head>
|
|
281
|
+
<body>
|
|
282
|
+
<h1>Sign-in Requests</h1>
|
|
283
|
+
<p>No pending sign-in requests.</p>
|
|
284
|
+
</body>
|
|
285
|
+
</html>\`,
|
|
286
|
+
{ cookies },
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const requestListHtml = requests
|
|
291
|
+
.map((request) => {
|
|
292
|
+
const scopeListHtml = request.scope
|
|
293
|
+
.map((scope) => \` <li>\${escapeHtml(scope)}</li>\`)
|
|
294
|
+
.join('\\n');
|
|
295
|
+
const bindingMessageHtml = request.bindingMessage
|
|
296
|
+
? \` <p>Confirm that your device is showing this message: <strong>\${escapeHtml(request.bindingMessage)}</strong></p>\\n\`
|
|
297
|
+
: '';
|
|
298
|
+
return \` <section>
|
|
299
|
+
<p>Client <strong>\${escapeHtml(request.clientId)}</strong> is requesting access to the following scopes:</p>
|
|
300
|
+
<ul>
|
|
301
|
+
\${scopeListHtml}
|
|
302
|
+
</ul>
|
|
303
|
+
\${bindingMessageHtml} <p>This request expires in \${remainingSeconds(request.expiresAt)} seconds.</p>
|
|
304
|
+
<form method="POST" action="/ciba/approve">
|
|
305
|
+
<input type="hidden" name="auth_req_id" value="\${escapeHtml(request.authReqId)}" />
|
|
306
|
+
<input type="hidden" name="csrf_token" value="\${escapeHtml(request.csrfToken ?? '')}" />
|
|
307
|
+
<button type="submit" name="decision" value="approve">Approve</button>
|
|
308
|
+
<button type="submit" name="decision" value="deny">Deny</button>
|
|
309
|
+
</form>
|
|
310
|
+
</section>\`;
|
|
311
|
+
})
|
|
312
|
+
.join('\\n');
|
|
313
|
+
|
|
314
|
+
return htmlResponse(
|
|
315
|
+
\`<!DOCTYPE html>
|
|
316
|
+
<html>
|
|
317
|
+
<head><title>Sign-in Requests</title></head>
|
|
318
|
+
<body>
|
|
319
|
+
<h1>Sign-in Requests</h1>
|
|
320
|
+
<p>Only approve a request you started yourself on another device.</p>
|
|
321
|
+
\${requestListHtml}
|
|
322
|
+
</body>
|
|
323
|
+
</html>\`,
|
|
324
|
+
{ cookies },
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** The decision-recorded screen. */
|
|
329
|
+
export function completedScreen(params: { approved: boolean; clientId: string }): Response {
|
|
330
|
+
const outcome = params.approved
|
|
331
|
+
? \`<p>You approved <strong>\${escapeHtml(params.clientId)}</strong>.</p>\`
|
|
332
|
+
: \`<p>You denied <strong>\${escapeHtml(params.clientId)}</strong>.</p>\`;
|
|
333
|
+
return htmlResponse(\`<!DOCTYPE html>
|
|
334
|
+
<html>
|
|
335
|
+
<head><title>Sign-in Requests</title></head>
|
|
336
|
+
<body>
|
|
337
|
+
<h1>Sign-in Requests</h1>
|
|
338
|
+
\${outcome}
|
|
339
|
+
<p>You can close this page and go back to your device.</p>
|
|
340
|
+
</body>
|
|
341
|
+
</html>\`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* A failed binding, CSRF or ownership check, shown on the OP's error page with
|
|
346
|
+
* its status. Anything that is not a verification failure is rethrown.
|
|
347
|
+
*/
|
|
348
|
+
export function verificationFailureScreen(error: unknown): Response {
|
|
349
|
+
if (error instanceof CibaVerificationError) {
|
|
350
|
+
return errorPage(error.message, error.statusCode);
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Remaining lifetime in whole seconds, never negative. */
|
|
356
|
+
export function remainingSeconds(expiresAt: Date): number {
|
|
357
|
+
return Math.max(0, Math.ceil((expiresAt.getTime() - Date.now()) / 1000));
|
|
358
|
+
}
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
export function nextJsCibaRouteTemplate() {
|
|
362
|
+
return `/**
|
|
363
|
+
* EXPERIMENTAL — CIBA authentication device UI, entry (CIBA Core 1.0 §7.1).
|
|
364
|
+
*
|
|
365
|
+
* CIBA leaves the authentication device — how the user is reached and how they
|
|
366
|
+
* authenticate — outside the specification. This UI is an OP-hosted page the
|
|
367
|
+
* user visits themselves: sign in at /ciba, review the pending requests
|
|
368
|
+
* addressed to you (client, scopes, binding_message), approve or deny. The
|
|
369
|
+
* consumption device learns the outcome only by polling the token endpoint.
|
|
370
|
+
*
|
|
371
|
+
* Backed by ${EXPERIMENTAL_PACKAGE}, whose API is NOT stable.
|
|
372
|
+
*/
|
|
373
|
+
import { createCibaLoginTransaction } from '${CIBA_PACKAGE}';
|
|
374
|
+
import { cibaLoginTransactionStore, stores } from '../_oidc-provider/provider';
|
|
375
|
+
import { buildCibaLoginBindingCookie, parseSessionId } from '../_oidc-provider/store';
|
|
376
|
+
import { loginScreen, pendingRequestsScreen, remainingSeconds } from './screens';
|
|
377
|
+
|
|
378
|
+
export const dynamic = 'force-dynamic';
|
|
379
|
+
export const runtime = 'nodejs';
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* With an OP session: the pending requests addressed to the signed-in user.
|
|
383
|
+
* Without one: a fresh login transaction and its sign-in form, bound to this
|
|
384
|
+
* browser by a cookie. A hidden csrf_token alone cannot stop login CSRF — the
|
|
385
|
+
* attacker can fetch a valid pair from their own form — so the submission must
|
|
386
|
+
* also present this browser's binding (see buildCibaLoginBindingCookie() in
|
|
387
|
+
* store.ts).
|
|
388
|
+
*/
|
|
389
|
+
export async function GET(request: Request): Promise<Response> {
|
|
390
|
+
const sessionId = parseSessionId(request.headers.get('Cookie'));
|
|
391
|
+
const session = sessionId ? await stores.browserSessionStore.get(sessionId) : undefined;
|
|
392
|
+
if (session) {
|
|
393
|
+
return pendingRequestsScreen(session.subject);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const { record, bindingSecret } = await createCibaLoginTransaction(cibaLoginTransactionStore);
|
|
397
|
+
return loginScreen(
|
|
398
|
+
{ loginTransactionId: record.id, csrfToken: record.csrfToken },
|
|
399
|
+
[buildCibaLoginBindingCookie(record.id, bindingSecret, remainingSeconds(record.expiresAt))],
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
export function nextJsCibaLoginRouteTemplate(corePkg) {
|
|
405
|
+
return `/**
|
|
406
|
+
* EXPERIMENTAL — CIBA authentication device UI, sign in.
|
|
407
|
+
*
|
|
408
|
+
* Binding first, then CSRF, then credentials: the binding proves this is the
|
|
409
|
+
* browser the login form was issued to, and it must gate the step that would
|
|
410
|
+
* otherwise let a forged POST plant an OP session in the victim's browser.
|
|
411
|
+
*
|
|
412
|
+
* Backed by ${EXPERIMENTAL_PACKAGE}, whose API is NOT stable.
|
|
413
|
+
*/
|
|
414
|
+
import { recordCibaLoginFailure, validateCibaLoginSubmission } from '${CIBA_PACKAGE}';
|
|
415
|
+
import { generateRandomString } from '${corePkg}';
|
|
416
|
+
import { errorPage } from '../../_oidc-provider/html';
|
|
417
|
+
import { readFormFields } from '../../_oidc-provider/http';
|
|
418
|
+
import { cibaLoginTransactionStore, stores } from '../../_oidc-provider/provider';
|
|
419
|
+
import {
|
|
420
|
+
buildClearedCibaLoginBindingCookie,
|
|
421
|
+
buildSessionCookie,
|
|
422
|
+
parseCibaLoginBindingSecret,
|
|
423
|
+
} from '../../_oidc-provider/store';
|
|
424
|
+
import { cibaConfig } from '../../backchannel_authentication/config';
|
|
425
|
+
import { loginScreen, pendingRequestsScreen, verificationFailureScreen } from '../screens';
|
|
426
|
+
|
|
427
|
+
export const dynamic = 'force-dynamic';
|
|
428
|
+
export const runtime = 'nodejs';
|
|
429
|
+
|
|
430
|
+
export async function POST(request: Request): Promise<Response> {
|
|
431
|
+
const form = await readFormFields(request);
|
|
432
|
+
const transactionId = String(form.get('login_transaction_id') ?? '');
|
|
433
|
+
|
|
434
|
+
let transaction;
|
|
435
|
+
try {
|
|
436
|
+
transaction = await validateCibaLoginSubmission({
|
|
437
|
+
transactionId,
|
|
438
|
+
csrfToken: String(form.get('csrf_token') ?? ''),
|
|
439
|
+
bindingSecret: parseCibaLoginBindingSecret(request.headers.get('Cookie'), transactionId),
|
|
440
|
+
store: cibaLoginTransactionStore,
|
|
441
|
+
});
|
|
442
|
+
} catch (error) {
|
|
443
|
+
return verificationFailureScreen(error);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Swap point: replace this with your own credential check (LDAP, WebAuthn, an
|
|
447
|
+
// upstream IdP) without touching anything above or below it.
|
|
448
|
+
const user = await stores.userStore.authenticate(
|
|
449
|
+
String(form.get('username') ?? ''),
|
|
450
|
+
String(form.get('password') ?? ''),
|
|
451
|
+
);
|
|
452
|
+
if (!user) {
|
|
453
|
+
// Per-transaction throttling only. Anyone can mint fresh login transactions
|
|
454
|
+
// by reloading /ciba, so the aggregate password-guess budget is the same as
|
|
455
|
+
// the one on /login. Subject-scoped throttling is a separate concern.
|
|
456
|
+
const failure = await recordCibaLoginFailure(
|
|
457
|
+
transaction,
|
|
458
|
+
cibaLoginTransactionStore,
|
|
459
|
+
cibaConfig.maxLoginAttempts,
|
|
460
|
+
);
|
|
461
|
+
if (!failure.canRetry) {
|
|
462
|
+
// The transaction is gone: this form cannot be retried at all.
|
|
463
|
+
return errorPage('Too many login attempts', 429);
|
|
464
|
+
}
|
|
465
|
+
return loginScreen({
|
|
466
|
+
loginTransactionId: transaction.id,
|
|
467
|
+
csrfToken: transaction.csrfToken,
|
|
468
|
+
error: 'Invalid credentials',
|
|
469
|
+
remainingAttempts: failure.remainingAttempts,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// The transaction is single-use: a successful login consumes it, and the
|
|
474
|
+
// session is established under a NEWLY minted id (never one the request
|
|
475
|
+
// brought along — session fixation).
|
|
476
|
+
await cibaLoginTransactionStore.delete(transaction.id);
|
|
477
|
+
const sessionId = generateRandomString(32);
|
|
478
|
+
await stores.browserSessionStore.set(sessionId, {
|
|
479
|
+
subject: user.sub,
|
|
480
|
+
authTime: Math.floor(Date.now() / 1000),
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Two cookies travel with the listing: the new OP session, and the cleared
|
|
484
|
+
// login binding (it is single-use and would otherwise linger until Max-Age).
|
|
485
|
+
return pendingRequestsScreen(user.sub, [
|
|
486
|
+
buildSessionCookie(sessionId),
|
|
487
|
+
buildClearedCibaLoginBindingCookie(transaction.id),
|
|
488
|
+
]);
|
|
489
|
+
}
|
|
490
|
+
`;
|
|
491
|
+
}
|
|
492
|
+
export function nextJsCibaApproveRouteTemplate(corePkg, scopes = []) {
|
|
493
|
+
const customScopesDeclared = scopes.length > 0;
|
|
494
|
+
const customScopeImport = customScopesDeclared
|
|
495
|
+
? `
|
|
496
|
+
import { resolveGrantableScopes } from '../../_oidc-provider/scopes';`
|
|
497
|
+
: '';
|
|
498
|
+
const approveNarrowStep = customScopesDeclared
|
|
499
|
+
? `
|
|
500
|
+
// Apply the scope policy to what was approved. approveCibaRequest() copies
|
|
501
|
+
// the requested scope into approvedScope, so the policy is applied
|
|
502
|
+
// afterwards and persisted; the token endpoint reads approvedScope, and
|
|
503
|
+
// RFC 6749 §3.3 allows a granted scope narrower than the request. Write the
|
|
504
|
+
// policy in resolveGrantableScopes() (scopes.ts).
|
|
505
|
+
approved.approvedScope = await resolveGrantableScopes(
|
|
506
|
+
approved.approvedScope ?? approved.scope,
|
|
507
|
+
session.subject,
|
|
508
|
+
);
|
|
509
|
+
await cibaAuthenticationRequestStore.update(approved);
|
|
510
|
+
`
|
|
511
|
+
: '';
|
|
512
|
+
return `/**
|
|
513
|
+
* EXPERIMENTAL — CIBA authentication device UI, approve or deny.
|
|
514
|
+
*
|
|
515
|
+
* The only state-changing step of the UI. It demands an OP session whose
|
|
516
|
+
* subject owns the record, plus the per-record csrf_token from the
|
|
517
|
+
* session-gated listing. No binding cookie is needed here: the approval is
|
|
518
|
+
* already bound to the authenticated session, so knowing an auth_req_id gives
|
|
519
|
+
* an attacker no step to forge.
|
|
520
|
+
*
|
|
521
|
+
* Backed by ${EXPERIMENTAL_PACKAGE}, whose API is NOT stable.
|
|
522
|
+
*/
|
|
523
|
+
import { approveCibaRequest, denyCibaRequest } from '${CIBA_PACKAGE}';
|
|
524
|
+
import { generateRandomString } from '${corePkg}';
|
|
525
|
+
import { errorPage } from '../../_oidc-provider/html';
|
|
526
|
+
import { readFormFields } from '../../_oidc-provider/http';
|
|
527
|
+
import { cibaAuthenticationRequestStore, resolvers, stores } from '../../_oidc-provider/provider';
|
|
528
|
+
import { parseSessionId } from '../../_oidc-provider/store';${customScopeImport}
|
|
529
|
+
import { completedScreen, verificationFailureScreen } from '../screens';
|
|
530
|
+
|
|
531
|
+
export const dynamic = 'force-dynamic';
|
|
532
|
+
export const runtime = 'nodejs';
|
|
533
|
+
|
|
534
|
+
export async function POST(request: Request): Promise<Response> {
|
|
535
|
+
const form = await readFormFields(request);
|
|
536
|
+
const authReqId = String(form.get('auth_req_id') ?? '');
|
|
537
|
+
const csrfToken = String(form.get('csrf_token') ?? '');
|
|
538
|
+
const decision = String(form.get('decision') ?? '');
|
|
539
|
+
|
|
540
|
+
const sessionId = parseSessionId(request.headers.get('Cookie'));
|
|
541
|
+
const session = sessionId ? await stores.browserSessionStore.get(sessionId) : undefined;
|
|
542
|
+
if (!session) {
|
|
543
|
+
return errorPage('Sign in again to review this request', 401);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (decision !== 'approve' && decision !== 'deny') {
|
|
547
|
+
return errorPage('invalid_request', 400, 'decision must be approve or deny');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
if (decision === 'approve') {
|
|
552
|
+
// subject and csrf_token are validated inside; the record moves to
|
|
553
|
+
// approved with auth_time, scope and a fresh grantId the token endpoint
|
|
554
|
+
// reads.
|
|
555
|
+
const grantId = generateRandomString(32);
|
|
556
|
+
const approved = await approveCibaRequest({
|
|
557
|
+
authReqId,
|
|
558
|
+
subject: session.subject,
|
|
559
|
+
csrfToken,
|
|
560
|
+
authTime: session.authTime,
|
|
561
|
+
grantId,
|
|
562
|
+
store: cibaAuthenticationRequestStore,
|
|
563
|
+
});
|
|
564
|
+
${approveNarrowStep}
|
|
565
|
+
// Record the consent the way /consent does, so a later Authorization Code
|
|
566
|
+
// Flow for this client skips the consent screen (OIDC Core 1.0 §3.1.2.4),
|
|
567
|
+
// and the grant, so withdrawing that consent revokes these tokens too.
|
|
568
|
+
await resolvers.consentResolver.recordConsent?.(
|
|
569
|
+
session.subject,
|
|
570
|
+
approved.clientId,
|
|
571
|
+
approved.approvedScope ?? approved.scope,
|
|
572
|
+
);
|
|
573
|
+
await resolvers.consentResolver.recordGrant(session.subject, approved.clientId, grantId);
|
|
574
|
+
return completedScreen({ approved: true, clientId: approved.clientId });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const record = await cibaAuthenticationRequestStore.findByAuthReqId(authReqId);
|
|
578
|
+
await denyCibaRequest({
|
|
579
|
+
authReqId,
|
|
580
|
+
subject: session.subject,
|
|
581
|
+
csrfToken,
|
|
582
|
+
store: cibaAuthenticationRequestStore,
|
|
583
|
+
});
|
|
584
|
+
return completedScreen({ approved: false, clientId: record?.clientId ?? '' });
|
|
585
|
+
} catch (error) {
|
|
586
|
+
return verificationFailureScreen(error);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
`;
|
|
590
|
+
}
|
|
591
|
+
//# sourceMappingURL=ciba.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ciba.js","sourceRoot":"","sources":["../../../src/frameworks/nextjs/ciba.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,YAAY,GAAG,GAAG,oBAAoB,OAAO,CAAC;AAGpD,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCR,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,4CAA4C,CAC1D,OAAe,EACf,QAAQ,GAAsB,gBAAgB,EAC9C,MAAM,GAAa,EAAE;IAErB,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/C,MAAM,iBAAiB,GAAG,oBAAoB;QAC5C,CAAC,CAAC;kEAC4D;QAC9D,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,eAAe,GAAG,oBAAoB;QAC1C,CAAC,CAAC;;;;;;;;;;;;;;;;;CAiBL;QACG,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;;;;KAIJ,oBAAoB;;;;;;;;;;;;;;;;;;;UAmBf,YAAY;;;;;;;;UAQZ,OAAO;;;;;;;;;;;;;;kCAciB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8CjD,eAAe;;;;;;;;;;;oCAWmB,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CAwB3E,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,yBAAyB,CAAC,MAAM,GAAa,EAAE;IAC7D,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/C,MAAM,iBAAiB,GAAG,oBAAoB;QAC5C,CAAC,CAAC;mEAC6D;QAC/D,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,WAAW,GAAG,oBAAoB;QACtC,CAAC,CAAC;;;;;;KAMD;QACD,CAAC,CAAC,6BAA6B,CAAC;IAClC,OAAO;;;;;;;kEAOyD,YAAY;;+EAEC,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuD9F,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFZ,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,uBAAuB;IACrC,OAAO;;;;;;;;;eASM,oBAAoB;;8CAEW,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BzD,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,4BAA4B,CAAC,OAAe;IAC1D,OAAO;;;;;;;eAOM,oBAAoB;;uEAEoC,YAAY;wCAC3C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2E9C,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,8BAA8B,CAAC,OAAe,EAAE,MAAM,GAAa,EAAE;IACnF,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/C,MAAM,iBAAiB,GAAG,oBAAoB;QAC5C,CAAC,CAAC;sEACgE;QAClE,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,iBAAiB,GAAG,oBAAoB;QAC5C,CAAC,CAAC;;;;;;;;;;;CAWL;QACG,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;;;;;;;;;eASM,oBAAoB;;uDAEoB,YAAY;wCAC3B,OAAO;;;;8DAIe,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoC7E,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;CAyBlB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../../../src/frameworks/nextjs/conformance.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,wBAAgB,6BAA6B,CAC3C,QAAQ,GAAE,iBAAoC,EAC9C,MAAM,GAAE,MAAM,EAAO,GACpB,MAAM,CA6BR"}
|