@idp.global/sdk 18.0.1 → 18.1.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/changelog.md +17 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_relyingparty/classes.actorscope.d.ts +23 -0
- package/dist_ts_relyingparty/classes.actorscope.js +22 -0
- package/dist_ts_relyingparty/classes.authorityobserver.d.ts +81 -0
- package/dist_ts_relyingparty/classes.authorityobserver.js +185 -0
- package/dist_ts_relyingparty/classes.errors.d.ts +33 -0
- package/dist_ts_relyingparty/classes.errors.js +53 -0
- package/dist_ts_relyingparty/classes.idpoidcprovider.d.ts +142 -0
- package/dist_ts_relyingparty/classes.idpoidcprovider.js +255 -0
- package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +84 -0
- package/dist_ts_relyingparty/classes.relyingpartyroutes.js +232 -0
- package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +210 -0
- package/dist_ts_relyingparty/classes.relyingpartysessions.js +786 -0
- package/dist_ts_relyingparty/classes.rpmodels.d.ts +79 -0
- package/dist_ts_relyingparty/classes.rpmodels.js +283 -0
- package/dist_ts_relyingparty/classes.secretbox.d.ts +14 -0
- package/dist_ts_relyingparty/classes.secretbox.js +45 -0
- package/dist_ts_relyingparty/classes.socketbinding.d.ts +75 -0
- package/dist_ts_relyingparty/classes.socketbinding.js +94 -0
- package/dist_ts_relyingparty/helpers.cookies.d.ts +11 -0
- package/dist_ts_relyingparty/helpers.cookies.js +62 -0
- package/dist_ts_relyingparty/helpers.crypto.d.ts +12 -0
- package/dist_ts_relyingparty/helpers.crypto.js +40 -0
- package/dist_ts_relyingparty/helpers.returnpath.d.ts +9 -0
- package/dist_ts_relyingparty/helpers.returnpath.js +39 -0
- package/dist_ts_relyingparty/index.d.ts +13 -0
- package/dist_ts_relyingparty/index.js +14 -0
- package/dist_ts_relyingparty/interfaces.d.ts +113 -0
- package/dist_ts_relyingparty/interfaces.js +2 -0
- package/dist_ts_relyingparty/plugins.d.ts +10 -0
- package/dist_ts_relyingparty/plugins.js +11 -0
- package/package.json +13 -10
- package/readme.md +158 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_relyingparty/classes.actorscope.ts +39 -0
- package/ts_relyingparty/classes.authorityobserver.ts +253 -0
- package/ts_relyingparty/classes.errors.ts +69 -0
- package/ts_relyingparty/classes.idpoidcprovider.ts +386 -0
- package/ts_relyingparty/classes.relyingpartyroutes.ts +286 -0
- package/ts_relyingparty/classes.relyingpartysessions.ts +970 -0
- package/ts_relyingparty/classes.rpmodels.ts +146 -0
- package/ts_relyingparty/classes.secretbox.ts +49 -0
- package/ts_relyingparty/classes.socketbinding.ts +142 -0
- package/ts_relyingparty/helpers.cookies.ts +79 -0
- package/ts_relyingparty/helpers.crypto.ts +49 -0
- package/ts_relyingparty/helpers.returnpath.ts +44 -0
- package/ts_relyingparty/index.ts +13 -0
- package/ts_relyingparty/interfaces.ts +133 -0
- package/ts_relyingparty/plugins.ts +17 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { OidcIntrospectionClient, OidcIntrospectionError, } from '../dist_ts_server/classes.oidc-introspection-client.js';
|
|
3
|
+
/** The provider could not be asked, or did not answer in its own contract. The caller fails closed on it. */
|
|
4
|
+
export class OidcAuthorityUnavailableError extends Error {
|
|
5
|
+
reason;
|
|
6
|
+
constructor(reason, causeArg) {
|
|
7
|
+
super('The identity provider could not be observed', { cause: causeArg });
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
this.name = 'OidcAuthorityUnavailableError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** The provider refused the refresh with `invalid_grant`: the refresh family is revoked, expired or was reused. */
|
|
13
|
+
export class OidcRefreshRefusedError extends Error {
|
|
14
|
+
constructor(causeArg) {
|
|
15
|
+
super('The identity provider refused the refresh', { cause: causeArg });
|
|
16
|
+
this.name = 'OidcRefreshRefusedError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class OidcCompletionError extends Error {
|
|
20
|
+
stage;
|
|
21
|
+
constructor(stage, causeArg) {
|
|
22
|
+
super('OIDC completion failed', { cause: causeArg });
|
|
23
|
+
this.stage = stage;
|
|
24
|
+
this.name = 'OidcCompletionError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The scopes a relying party asks for. `organizations` and `roles` are what
|
|
29
|
+
* idp.global reports memberships under.
|
|
30
|
+
*/
|
|
31
|
+
export const IDP_OIDC_DEFAULT_SCOPES = 'openid profile email organizations roles';
|
|
32
|
+
export const IDP_OIDC_DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
|
|
33
|
+
const optionalString = (valueArg) => typeof valueArg === 'string' && valueArg.trim().length > 0 ? valueArg.trim() : undefined;
|
|
34
|
+
/** When an access token dies, from the `expires_in` of a token response; `0` when it names none. */
|
|
35
|
+
const expiryOf = (expiresInArg) => typeof expiresInArg === 'number' && Number.isFinite(expiresInArg) && expiresInArg > 0
|
|
36
|
+
? Date.now() + Math.floor(expiresInArg) * 1_000
|
|
37
|
+
: 0;
|
|
38
|
+
/**
|
|
39
|
+
* Organization claims as idp.global documents them: `id`, `name`, `slug` and
|
|
40
|
+
* the member's roles. Anything that is not that exact shape is dropped rather
|
|
41
|
+
* than guessed at.
|
|
42
|
+
*/
|
|
43
|
+
export const readOrganizationClaims = (valueArg) => {
|
|
44
|
+
if (!Array.isArray(valueArg)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const organizations = [];
|
|
48
|
+
for (const entry of valueArg) {
|
|
49
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const candidate = entry;
|
|
53
|
+
const id = optionalString(candidate.id);
|
|
54
|
+
const name = optionalString(candidate.name);
|
|
55
|
+
const slug = optionalString(candidate.slug);
|
|
56
|
+
if (!id || !name || !slug) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const roles = Array.isArray(candidate.roles)
|
|
60
|
+
? candidate.roles.filter((roleArg) => typeof roleArg === 'string')
|
|
61
|
+
: [];
|
|
62
|
+
organizations.push({ id, name, slug, roles });
|
|
63
|
+
}
|
|
64
|
+
return organizations;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The relying-party side of the authorization code flow with S256 PKCE on
|
|
68
|
+
* `openid-client`, in the profile idp.global supports: code responses, S256
|
|
69
|
+
* challenges, RS256 ID tokens and `client_secret_basic` for a confidential
|
|
70
|
+
* client. Live organization authority is observed with the sdk's
|
|
71
|
+
* `OidcIntrospectionClient`.
|
|
72
|
+
*/
|
|
73
|
+
export class IdpOidcProvider {
|
|
74
|
+
requestTimeoutSeconds;
|
|
75
|
+
#options;
|
|
76
|
+
#configuration;
|
|
77
|
+
#introspection;
|
|
78
|
+
constructor(optionsArg) {
|
|
79
|
+
if (!(optionsArg?.issuer instanceof URL) || !(optionsArg.redirectUri instanceof URL)) {
|
|
80
|
+
throw new Error('IdpOidcProvider needs the issuer and the redirect URI as URLs');
|
|
81
|
+
}
|
|
82
|
+
if (!optionalString(optionsArg.clientId) || !optionalString(optionsArg.clientSecret)) {
|
|
83
|
+
throw new Error('IdpOidcProvider needs a client id and a client secret');
|
|
84
|
+
}
|
|
85
|
+
const timeout = optionsArg.requestTimeoutSeconds ?? IDP_OIDC_DEFAULT_REQUEST_TIMEOUT_SECONDS;
|
|
86
|
+
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 60) {
|
|
87
|
+
throw new Error('IdpOidcProvider requestTimeoutSeconds must be an integer from 1 to 60');
|
|
88
|
+
}
|
|
89
|
+
this.requestTimeoutSeconds = timeout;
|
|
90
|
+
this.#options = optionsArg;
|
|
91
|
+
}
|
|
92
|
+
get configuration() {
|
|
93
|
+
if (!this.#configuration) {
|
|
94
|
+
throw new Error('The identity provider is not initialized');
|
|
95
|
+
}
|
|
96
|
+
return this.#configuration;
|
|
97
|
+
}
|
|
98
|
+
async init() {
|
|
99
|
+
if (this.#configuration) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const options = this.#options;
|
|
103
|
+
// the timeout given to discovery bounds discovery itself and stays on the
|
|
104
|
+
// resolved configuration for every later request
|
|
105
|
+
const configuration = await plugins.openidClient.discovery(options.issuer, options.clientId, {
|
|
106
|
+
client_secret: options.clientSecret,
|
|
107
|
+
token_endpoint_auth_method: 'client_secret_basic',
|
|
108
|
+
id_token_signed_response_alg: 'RS256',
|
|
109
|
+
}, plugins.openidClient.ClientSecretBasic(options.clientSecret), { timeout: this.requestTimeoutSeconds });
|
|
110
|
+
const metadata = configuration.serverMetadata();
|
|
111
|
+
if (!metadata.authorization_endpoint || !metadata.token_endpoint || !metadata.jwks_uri) {
|
|
112
|
+
throw new Error('The identity provider is missing required OIDC endpoints');
|
|
113
|
+
}
|
|
114
|
+
if (!metadata.code_challenge_methods_supported?.includes('S256')) {
|
|
115
|
+
throw new Error('The identity provider must support PKCE S256');
|
|
116
|
+
}
|
|
117
|
+
if (metadata.id_token_signing_alg_values_supported
|
|
118
|
+
&& !metadata.id_token_signing_alg_values_supported.includes('RS256')) {
|
|
119
|
+
throw new Error('The identity provider must sign ID tokens with RS256');
|
|
120
|
+
}
|
|
121
|
+
if (metadata.grant_types_supported && !metadata.grant_types_supported.includes('authorization_code')) {
|
|
122
|
+
throw new Error('The identity provider must support the authorization_code grant');
|
|
123
|
+
}
|
|
124
|
+
if (metadata.grant_types_supported && !metadata.grant_types_supported.includes('refresh_token')) {
|
|
125
|
+
// without rotation the access token dies long before the session does
|
|
126
|
+
throw new Error('The identity provider must support the refresh_token grant');
|
|
127
|
+
}
|
|
128
|
+
this.#introspection = new OidcIntrospectionClient({
|
|
129
|
+
issuer: options.issuer.origin,
|
|
130
|
+
clientId: options.clientId,
|
|
131
|
+
clientSecret: options.clientSecret,
|
|
132
|
+
...(options.introspectionTimeoutMs !== undefined ? { timeoutMs: options.introspectionTimeoutMs } : {}),
|
|
133
|
+
});
|
|
134
|
+
this.#configuration = configuration;
|
|
135
|
+
}
|
|
136
|
+
async buildAuthorizationUrl(optionsArg) {
|
|
137
|
+
const codeChallenge = await plugins.openidClient.calculatePKCECodeChallenge(optionsArg.pkceVerifier);
|
|
138
|
+
return plugins.openidClient.buildAuthorizationUrl(this.configuration, {
|
|
139
|
+
redirect_uri: this.#options.redirectUri.href,
|
|
140
|
+
response_type: 'code',
|
|
141
|
+
scope: this.#options.scopes ?? IDP_OIDC_DEFAULT_SCOPES,
|
|
142
|
+
code_challenge: codeChallenge,
|
|
143
|
+
code_challenge_method: 'S256',
|
|
144
|
+
state: optionsArg.state,
|
|
145
|
+
nonce: optionsArg.nonce,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async completeAuthorization(optionsArg) {
|
|
149
|
+
let tokens;
|
|
150
|
+
try {
|
|
151
|
+
tokens = await plugins.openidClient.authorizationCodeGrant(this.configuration, optionsArg.callbackUrl, {
|
|
152
|
+
pkceCodeVerifier: optionsArg.pkceVerifier,
|
|
153
|
+
expectedState: optionsArg.state,
|
|
154
|
+
expectedNonce: optionsArg.nonce,
|
|
155
|
+
idTokenExpected: true,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw new OidcCompletionError('token_exchange', error);
|
|
160
|
+
}
|
|
161
|
+
let subject;
|
|
162
|
+
try {
|
|
163
|
+
const claims = tokens.claims();
|
|
164
|
+
if (!claims?.sub) {
|
|
165
|
+
throw new Error('The token response carries no subject');
|
|
166
|
+
}
|
|
167
|
+
subject = claims.sub;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
throw new OidcCompletionError('token_validation', error);
|
|
171
|
+
}
|
|
172
|
+
// UserInfo is the current view of profile and organization claims; the ID
|
|
173
|
+
// token proves the login. Both are read, and the subjects must agree.
|
|
174
|
+
let userInfo;
|
|
175
|
+
try {
|
|
176
|
+
userInfo = await plugins.openidClient.fetchUserInfo(this.configuration, tokens.access_token, subject);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new OidcCompletionError('userinfo', error);
|
|
180
|
+
}
|
|
181
|
+
if (userInfo.sub !== subject) {
|
|
182
|
+
throw new OidcCompletionError('token_validation', new Error('The UserInfo subject does not match the ID token'));
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
subject,
|
|
186
|
+
accessToken: tokens.access_token,
|
|
187
|
+
...(tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {}),
|
|
188
|
+
accessTokenExpiresAt: expiryOf(tokens.expires_in),
|
|
189
|
+
email: optionalString(userInfo.email),
|
|
190
|
+
emailVerified: userInfo.email_verified === true,
|
|
191
|
+
displayName: optionalString(userInfo.name) || optionalString(userInfo.preferred_username),
|
|
192
|
+
organizations: readOrganizationClaims(Reflect.get(userInfo, 'organizations')),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async revokeToken(tokenArg, hintArg) {
|
|
196
|
+
await plugins.openidClient.tokenRevocation(this.configuration, tokenArg, { token_type_hint: hintArg });
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* One `POST /oauth/introspect` through the confidential introspection
|
|
200
|
+
* client, which validates the whole answer before this code sees it. A
|
|
201
|
+
* refusal (`active: false`) is an answer; anything else is a failure to
|
|
202
|
+
* observe.
|
|
203
|
+
*/
|
|
204
|
+
async introspectOrganization(optionsArg) {
|
|
205
|
+
if (!this.#introspection) {
|
|
206
|
+
throw new Error('The identity provider is not initialized');
|
|
207
|
+
}
|
|
208
|
+
let response;
|
|
209
|
+
try {
|
|
210
|
+
response = await this.#introspection.introspect({
|
|
211
|
+
token: optionsArg.accessToken,
|
|
212
|
+
subject: optionsArg.subject,
|
|
213
|
+
organizationId: optionsArg.organizationId,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (error instanceof OidcIntrospectionError) {
|
|
218
|
+
if (error.code === 'invalid_request') {
|
|
219
|
+
// a request the endpoint's own client refuses is a defect of the
|
|
220
|
+
// caller, never an answer about access
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
throw new OidcAuthorityUnavailableError(error.code, error);
|
|
224
|
+
}
|
|
225
|
+
throw new OidcAuthorityUnavailableError('unavailable', error);
|
|
226
|
+
}
|
|
227
|
+
if (!response.active) {
|
|
228
|
+
return { active: false, roles: [], expiresAt: 0 };
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
active: true,
|
|
232
|
+
roles: response.idp_global_authority.roles,
|
|
233
|
+
expiresAt: response.exp * 1_000,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** The refresh grant with rotation; a superseded refresh token is never kept by the caller. */
|
|
237
|
+
async refreshTokens(refreshTokenArg) {
|
|
238
|
+
let tokens;
|
|
239
|
+
try {
|
|
240
|
+
tokens = await plugins.openidClient.refreshTokenGrant(this.configuration, refreshTokenArg);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (error instanceof plugins.openidClient.ResponseBodyError && error.error === 'invalid_grant') {
|
|
244
|
+
throw new OidcRefreshRefusedError(error);
|
|
245
|
+
}
|
|
246
|
+
throw new OidcAuthorityUnavailableError('unavailable', error);
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
accessToken: tokens.access_token,
|
|
250
|
+
...(tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {}),
|
|
251
|
+
accessTokenExpiresAt: expiryOf(tokens.expires_in),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pZHBvaWRjcHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19yZWx5aW5ncGFydHkvY2xhc3Nlcy5pZHBvaWRjcHJvdmlkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUNMLHVCQUF1QixFQUN2QixzQkFBc0IsR0FDdkIsTUFBTSxtREFBbUQsQ0FBQztBQTBEM0QsNkdBQTZHO0FBQzdHLE1BQU0sT0FBTyw2QkFBOEIsU0FBUSxLQUFLO0lBRXBDO0lBRGxCLFlBQ2tCLE1BQTZCLEVBQzdDLFFBQWtCO1FBRWxCLEtBQUssQ0FBQyw2Q0FBNkMsRUFBRSxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBSDFELFdBQU0sR0FBTixNQUFNLENBQXVCO1FBSTdDLElBQUksQ0FBQyxJQUFJLEdBQUcsK0JBQStCLENBQUM7SUFDOUMsQ0FBQztDQUNGO0FBRUQsbUhBQW1IO0FBQ25ILE1BQU0sT0FBTyx1QkFBd0IsU0FBUSxLQUFLO0lBQ2hELFlBQVksUUFBa0I7UUFDNUIsS0FBSyxDQUFDLDJDQUEyQyxFQUFFLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDeEUsSUFBSSxDQUFDLElBQUksR0FBRyx5QkFBeUIsQ0FBQztJQUN4QyxDQUFDO0NBQ0Y7QUFNRCxNQUFNLE9BQU8sbUJBQW9CLFNBQVEsS0FBSztJQUUxQjtJQURsQixZQUNrQixLQUFrQyxFQUNsRCxRQUFpQjtRQUVqQixLQUFLLENBQUMsd0JBQXdCLEVBQUUsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUhyQyxVQUFLLEdBQUwsS0FBSyxDQUE2QjtRQUlsRCxJQUFJLENBQUMsSUFBSSxHQUFHLHFCQUFxQixDQUFDO0lBQ3BDLENBQUM7Q0FDRjtBQTBCRDs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSx1QkFB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
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { RelyingPartySessions } from './classes.relyingpartysessions.js';
|
|
2
|
+
import type { IResolvedRelyingPartySession } from './interfaces.js';
|
|
3
|
+
/**
|
|
4
|
+
* The part of an HTTP request context the routes read. `IRequestContext` of
|
|
5
|
+
* `@push.rocks/smartserve` — what a typedserver `IRouteHandler` receives —
|
|
6
|
+
* satisfies it, so the routes plug into `addRoute()` / `addCustomRoutes()`
|
|
7
|
+
* without the sdk depending on either package.
|
|
8
|
+
*/
|
|
9
|
+
export interface IRelyingPartyHttpContext {
|
|
10
|
+
readonly method: string;
|
|
11
|
+
readonly url: URL;
|
|
12
|
+
readonly headers: Headers;
|
|
13
|
+
readonly request: Request;
|
|
14
|
+
}
|
|
15
|
+
export interface IRelyingPartyRoutesOptions {
|
|
16
|
+
sessions: RelyingPartySessions;
|
|
17
|
+
/** where a sign-in that did not finish sends the browser; no provider text is ever echoed into it */
|
|
18
|
+
failureLocation: string;
|
|
19
|
+
/**
|
|
20
|
+
* Where a refusal of the application (`RelyingPartyRefusal.code`) sends the
|
|
21
|
+
* browser, so the application can explain it; a refusal not named here is
|
|
22
|
+
* answered as a failed authentication with its status.
|
|
23
|
+
*/
|
|
24
|
+
refusalLocations?: Readonly<Record<string, string>>;
|
|
25
|
+
/**
|
|
26
|
+
* The body `GET <prefix>session` answers with. Default:
|
|
27
|
+
* `{ session: { sessionId, principalId, expiresAt, csrfToken } | null }`.
|
|
28
|
+
*/
|
|
29
|
+
sessionAnswer?(resolvedArg: IResolvedRelyingPartySession | null): Promise<object>;
|
|
30
|
+
/** the largest logout body read, in bytes; a longer one is cancelled as soon as it passes the limit */
|
|
31
|
+
maxBodyBytes?: number;
|
|
32
|
+
/**
|
|
33
|
+
* diagnostics of a failed start or callback; the default writes one JSON
|
|
34
|
+
* line to `console.error`. A throwing sink never changes the answer: the
|
|
35
|
+
* event and its failure go to `console.error` instead.
|
|
36
|
+
*/
|
|
37
|
+
onRouteEvent?(eventArg: {
|
|
38
|
+
event: 'auth_login_start_failed' | 'auth_callback_failed' | 'auth_callback_declined' | 'auth_callback_refused';
|
|
39
|
+
stage?: string;
|
|
40
|
+
code?: string;
|
|
41
|
+
correlationId?: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
}): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The HTTP steps of a relying-party login, all under the sessions' login path
|
|
47
|
+
* prefix (default `/auth/`):
|
|
48
|
+
*
|
|
49
|
+
* - `GET login?return=…` redirects to the provider and sets the intent cookie
|
|
50
|
+
* - `GET callback` consumes the attempt once and sets the session cookie; a
|
|
51
|
+
* provider `error` abandons the attempt
|
|
52
|
+
* - `GET finalize` releases the return path only to the browser holding the
|
|
53
|
+
* session this attempt created
|
|
54
|
+
* - `GET session` answers the current session
|
|
55
|
+
* - `POST logout` ends the session; it needs this origin and the CSRF token
|
|
56
|
+
*
|
|
57
|
+
* `handle()` answers `null` for every other request, so the application
|
|
58
|
+
* chains its own routes (a local password login) around it. Cookies are only
|
|
59
|
+
* ever set here: a `/typedrequest` answer cannot carry `Set-Cookie`.
|
|
60
|
+
*/
|
|
61
|
+
export declare class RelyingPartyRoutes {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(optionsArg: IRelyingPartyRoutesOptions);
|
|
64
|
+
/** The paths the routes answer, for registering them one by one. */
|
|
65
|
+
get paths(): {
|
|
66
|
+
login: string;
|
|
67
|
+
callback: string;
|
|
68
|
+
finalize: string;
|
|
69
|
+
session: string;
|
|
70
|
+
logout: string;
|
|
71
|
+
};
|
|
72
|
+
readonly handle: (contextArg: IRelyingPartyHttpContext) => Promise<Response | null>;
|
|
73
|
+
private login;
|
|
74
|
+
private callback;
|
|
75
|
+
private logout;
|
|
76
|
+
private sessionAnswer;
|
|
77
|
+
/**
|
|
78
|
+
* A JSON body within `maxBodyBytes`, or `null`. The limit holds while the
|
|
79
|
+
* body streams, whatever `Content-Length` claims or omits: the read is
|
|
80
|
+
* cancelled at the first chunk that passes it.
|
|
81
|
+
*/
|
|
82
|
+
private readJsonBody;
|
|
83
|
+
private emit;
|
|
84
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { LoginCallbackError, RelyingPartyError, RelyingPartyRefusal } from './classes.errors.js';
|
|
3
|
+
const DEFAULT_MAX_BODY_BYTES = 16 * 1024;
|
|
4
|
+
/** What the provider names in a callback it did not issue a code for. */
|
|
5
|
+
const MAX_PROVIDER_ERROR_CODE_LENGTH = 64;
|
|
6
|
+
const noStoreHeaders = (headersArg) => {
|
|
7
|
+
const headers = new Headers(headersArg);
|
|
8
|
+
headers.set('Cache-Control', 'no-store');
|
|
9
|
+
headers.set('Pragma', 'no-cache');
|
|
10
|
+
headers.set('Cross-Origin-Resource-Policy', 'same-origin');
|
|
11
|
+
return headers;
|
|
12
|
+
};
|
|
13
|
+
const jsonResponse = (bodyArg, statusArg, headersArg) => {
|
|
14
|
+
const headers = noStoreHeaders(headersArg);
|
|
15
|
+
headers.set('Content-Type', 'application/json; charset=utf-8');
|
|
16
|
+
return new Response(JSON.stringify(bodyArg), { status: statusArg, headers });
|
|
17
|
+
};
|
|
18
|
+
const redirect = (locationArg, headersArg) => new Response(null, { status: 302, headers: noStoreHeaders({ Location: locationArg, ...headersArg }) });
|
|
19
|
+
/**
|
|
20
|
+
* The HTTP steps of a relying-party login, all under the sessions' login path
|
|
21
|
+
* prefix (default `/auth/`):
|
|
22
|
+
*
|
|
23
|
+
* - `GET login?return=…` redirects to the provider and sets the intent cookie
|
|
24
|
+
* - `GET callback` consumes the attempt once and sets the session cookie; a
|
|
25
|
+
* provider `error` abandons the attempt
|
|
26
|
+
* - `GET finalize` releases the return path only to the browser holding the
|
|
27
|
+
* session this attempt created
|
|
28
|
+
* - `GET session` answers the current session
|
|
29
|
+
* - `POST logout` ends the session; it needs this origin and the CSRF token
|
|
30
|
+
*
|
|
31
|
+
* `handle()` answers `null` for every other request, so the application
|
|
32
|
+
* chains its own routes (a local password login) around it. Cookies are only
|
|
33
|
+
* ever set here: a `/typedrequest` answer cannot carry `Set-Cookie`.
|
|
34
|
+
*/
|
|
35
|
+
export class RelyingPartyRoutes {
|
|
36
|
+
#sessions;
|
|
37
|
+
#options;
|
|
38
|
+
#maxBodyBytes;
|
|
39
|
+
constructor(optionsArg) {
|
|
40
|
+
this.#sessions = optionsArg.sessions;
|
|
41
|
+
this.#options = optionsArg;
|
|
42
|
+
this.#maxBodyBytes = optionsArg.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
43
|
+
}
|
|
44
|
+
/** The paths the routes answer, for registering them one by one. */
|
|
45
|
+
get paths() {
|
|
46
|
+
const prefix = this.#sessions.loginPathPrefix;
|
|
47
|
+
return {
|
|
48
|
+
login: prefix + 'login',
|
|
49
|
+
callback: prefix + 'callback',
|
|
50
|
+
finalize: prefix + 'finalize',
|
|
51
|
+
session: prefix + 'session',
|
|
52
|
+
logout: prefix + 'logout',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
handle = async (contextArg) => {
|
|
56
|
+
const paths = this.paths;
|
|
57
|
+
const path = contextArg.url.pathname;
|
|
58
|
+
const method = contextArg.method.toUpperCase();
|
|
59
|
+
const providerLogin = this.#sessions.providerLoginEnabled;
|
|
60
|
+
if (path === paths.login && method === 'GET' && providerLogin) {
|
|
61
|
+
return this.login(contextArg);
|
|
62
|
+
}
|
|
63
|
+
if (path === paths.callback && method === 'GET' && providerLogin) {
|
|
64
|
+
return this.callback(contextArg);
|
|
65
|
+
}
|
|
66
|
+
if (path === paths.finalize && method === 'GET' && providerLogin) {
|
|
67
|
+
let location = this.#options.failureLocation;
|
|
68
|
+
try {
|
|
69
|
+
location = await this.#sessions.finalizeLogin(contextArg.headers.get('cookie'));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
location = this.#options.failureLocation;
|
|
73
|
+
}
|
|
74
|
+
return redirect(location, { 'Set-Cookie': this.#sessions.clearIntentCookie() });
|
|
75
|
+
}
|
|
76
|
+
if (path === paths.session && method === 'GET') {
|
|
77
|
+
const resolved = await this.#sessions.resolveCookieSession(contextArg.headers.get('cookie'));
|
|
78
|
+
return jsonResponse(await this.sessionAnswer(resolved), 200);
|
|
79
|
+
}
|
|
80
|
+
if (path === paths.logout && method === 'POST') {
|
|
81
|
+
return this.logout(contextArg);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
async login(contextArg) {
|
|
86
|
+
try {
|
|
87
|
+
const start = await this.#sessions.startLogin(contextArg.url.searchParams.get('return'));
|
|
88
|
+
return redirect(start.authorizationUrl.href, { 'Set-Cookie': start.intentCookie });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
this.emit({ event: 'auth_login_start_failed', message: error instanceof Error ? error.message : String(error) });
|
|
92
|
+
return jsonResponse({ error: 'authentication_unavailable' }, 503);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async callback(contextArg) {
|
|
96
|
+
// the callback is read from the public origin, never from a forwarded host
|
|
97
|
+
const callbackUrl = new URL(contextArg.url.pathname + contextArg.url.search, this.#sessions.publicOrigin);
|
|
98
|
+
const cookieHeader = contextArg.headers.get('cookie');
|
|
99
|
+
const providerError = callbackUrl.searchParams.get('error');
|
|
100
|
+
if (providerError) {
|
|
101
|
+
// the user cancelled, or the provider refused: nothing to exchange, and
|
|
102
|
+
// the attempt is consumed so its state never completes later
|
|
103
|
+
await this.#sessions.abandonLogin(callbackUrl, cookieHeader);
|
|
104
|
+
this.emit({ event: 'auth_callback_declined', code: providerError.slice(0, MAX_PROVIDER_ERROR_CODE_LENGTH) });
|
|
105
|
+
return redirect(this.#options.failureLocation, { 'Set-Cookie': this.#sessions.clearIntentCookie() });
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const opening = await this.#sessions.completeLogin(callbackUrl, cookieHeader);
|
|
109
|
+
return redirect(this.paths.finalize, { 'Set-Cookie': opening.sessionCookie });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const clearIntent = { 'Set-Cookie': this.#sessions.clearIntentCookie() };
|
|
113
|
+
if (error instanceof RelyingPartyRefusal) {
|
|
114
|
+
this.emit({ event: 'auth_callback_refused', code: error.code });
|
|
115
|
+
const locations = this.#options.refusalLocations ?? {};
|
|
116
|
+
if (Object.hasOwn(locations, error.code)) {
|
|
117
|
+
return redirect(locations[error.code], clearIntent);
|
|
118
|
+
}
|
|
119
|
+
return jsonResponse({ error: 'authentication_failed' }, error.status, clearIntent);
|
|
120
|
+
}
|
|
121
|
+
const status = error instanceof RelyingPartyError ? error.status : 502;
|
|
122
|
+
if (status >= 500) {
|
|
123
|
+
this.emit({
|
|
124
|
+
event: 'auth_callback_failed',
|
|
125
|
+
correlationId: plugins.crypto.randomUUID(),
|
|
126
|
+
stage: error instanceof LoginCallbackError ? error.stage : 'unclassified',
|
|
127
|
+
message: error instanceof Error ? error.message : String(error),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return jsonResponse({ error: 'authentication_failed' }, status, clearIntent);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async logout(contextArg) {
|
|
134
|
+
if (contextArg.headers.get('origin') !== this.#sessions.publicOrigin) {
|
|
135
|
+
return jsonResponse({ error: 'forbidden' }, 403);
|
|
136
|
+
}
|
|
137
|
+
const clearSession = { 'Set-Cookie': this.#sessions.clearSessionCookie() };
|
|
138
|
+
const resolved = await this.#sessions.resolveCookieSession(contextArg.headers.get('cookie'));
|
|
139
|
+
if (!resolved) {
|
|
140
|
+
return jsonResponse({ endedLiveSession: false }, 200, clearSession);
|
|
141
|
+
}
|
|
142
|
+
const body = await this.readJsonBody(contextArg);
|
|
143
|
+
const csrfToken = body && typeof body === 'object' ? Reflect.get(body, 'csrfToken') : undefined;
|
|
144
|
+
if (!this.#sessions.validateCsrf(resolved, csrfToken)) {
|
|
145
|
+
return jsonResponse({ error: 'forbidden' }, 403);
|
|
146
|
+
}
|
|
147
|
+
return jsonResponse({ endedLiveSession: await this.#sessions.logout(resolved.session.id) }, 200, clearSession);
|
|
148
|
+
}
|
|
149
|
+
async sessionAnswer(resolvedArg) {
|
|
150
|
+
if (this.#options.sessionAnswer) {
|
|
151
|
+
return this.#options.sessionAnswer(resolvedArg);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
session: resolvedArg
|
|
155
|
+
? {
|
|
156
|
+
sessionId: resolvedArg.session.id,
|
|
157
|
+
principalId: resolvedArg.session.principalId,
|
|
158
|
+
expiresAt: resolvedArg.session.expiresAt,
|
|
159
|
+
csrfToken: resolvedArg.csrfToken,
|
|
160
|
+
}
|
|
161
|
+
: null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* A JSON body within `maxBodyBytes`, or `null`. The limit holds while the
|
|
166
|
+
* body streams, whatever `Content-Length` claims or omits: the read is
|
|
167
|
+
* cancelled at the first chunk that passes it.
|
|
168
|
+
*/
|
|
169
|
+
async readJsonBody(contextArg) {
|
|
170
|
+
const declaredLength = Number(contextArg.headers.get('content-length') || 0);
|
|
171
|
+
if (!Number.isFinite(declaredLength) || declaredLength < 0 || declaredLength > this.#maxBodyBytes) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const body = contextArg.request.body;
|
|
175
|
+
if (!body) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const reader = body.getReader();
|
|
179
|
+
const chunks = [];
|
|
180
|
+
let received = 0;
|
|
181
|
+
try {
|
|
182
|
+
for (;;) {
|
|
183
|
+
const { done, value } = await reader.read();
|
|
184
|
+
if (done) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
received += value.byteLength;
|
|
188
|
+
if (received > this.#maxBodyBytes) {
|
|
189
|
+
await reader.cancel();
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
chunks.push(value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
reader.releaseLock();
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
return JSON.parse(Buffer.concat(chunks, received).toString('utf8'));
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
emit(eventArg) {
|
|
209
|
+
if (!this.#options.onRouteEvent) {
|
|
210
|
+
console.error(JSON.stringify(eventArg));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const reportFailure = (errorArg) => {
|
|
214
|
+
console.error(JSON.stringify({
|
|
215
|
+
event: 'hook_failed',
|
|
216
|
+
hook: 'onRouteEvent',
|
|
217
|
+
message: errorArg instanceof Error ? errorArg.message : String(errorArg),
|
|
218
|
+
}));
|
|
219
|
+
console.error(JSON.stringify(eventArg));
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
const result = this.#options.onRouteEvent(eventArg);
|
|
223
|
+
if (result instanceof Promise) {
|
|
224
|
+
result.catch(reportFailure);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
reportFailure(error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZWx5aW5ncGFydHlyb3V0ZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19yZWx5aW5ncGFydHkvY2xhc3Nlcy5yZWx5aW5ncGFydHlyb3V0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLGlCQUFpQixFQUFFLG1CQUFtQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFnRGpHLE1BQU0sc0JBQXNCLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUN6Qyx5RUFBeUU7QUFDekUsTUFBTSw4QkFBOEIsR0FBRyxFQUFFLENBQUM7QUFFMUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxVQUFtQyxFQUFXLEVBQUU7SUFDdEUsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDeEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDekMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDbEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyw4QkFBOEIsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUMzRCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDLENBQUM7QUFFRixNQUFNLFlBQVksR0FBRyxDQUFDLE9BQWUsRUFBRSxTQUFpQixFQUFFLFVBQW1DLEVBQVksRUFBRTtJQUN6RyxNQUFNLE9BQU8sR0FBRyxjQUFjLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDM0MsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsaUNBQWlDLENBQUMsQ0FBQztJQUMvRCxPQUFPLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxDQUFDLENB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
|