@idp.global/sdk 19.4.0 → 19.5.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 +26 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_browser/classes.relyingpartybearerclient.d.ts +40 -3
- package/dist_ts_browser/classes.relyingpartybearerclient.js +67 -12
- package/dist_ts_relyingparty/classes.errors.d.ts +2 -0
- package/dist_ts_relyingparty/classes.errors.js +2 -1
- package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +64 -9
- package/dist_ts_relyingparty/classes.relyingpartysessions.js +158 -31
- package/dist_ts_relyingparty/contract.bearer.d.ts +37 -0
- package/dist_ts_relyingparty/contract.bearer.js +5 -1
- package/dist_ts_relyingparty/helpers.bearer.d.ts +2 -3
- package/dist_ts_relyingparty/helpers.bearer.js +4 -6
- package/dist_ts_relyingparty/interfaces.d.ts +42 -3
- package/package.json +1 -1
- package/readme.md +156 -10
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_browser/classes.relyingpartybearerclient.ts +98 -18
- package/ts_relyingparty/classes.errors.ts +3 -0
- package/ts_relyingparty/classes.relyingpartysessions.ts +170 -36
- package/ts_relyingparty/contract.bearer.ts +46 -0
- package/ts_relyingparty/helpers.bearer.ts +3 -6
- package/ts_relyingparty/interfaces.ts +44 -7
|
@@ -36,6 +36,43 @@ export interface IRelyingPartyBearerMessage {
|
|
|
36
36
|
* of the application's own refusal (`RelyingPartyRefusal.code`).
|
|
37
37
|
*/
|
|
38
38
|
export type TRelyingPartyBearerDeliveryError = 'access_denied' | 'authentication_failed' | (string & {});
|
|
39
|
+
/** Whether a value is a well-formed PKCE code verifier (RFC 7636 §4.1). */
|
|
40
|
+
export declare const isPkceVerifier: (valueArg: unknown) => valueArg is string;
|
|
41
|
+
/**
|
|
42
|
+
* The query of `GET <prefix>bearer/login`, by its parameter names on the
|
|
43
|
+
* wire. The routes check every parameter as untrusted input.
|
|
44
|
+
*/
|
|
45
|
+
export interface IRelyingPartyBearerLoginQuery {
|
|
46
|
+
client_kind: TRelyingPartyBearerClientKind;
|
|
47
|
+
/** one of the configured return targets of the kind, exactly; a loopback target with the client's port */
|
|
48
|
+
return_target: string;
|
|
49
|
+
/** BASE64URL(SHA-256(code verifier)), 43 characters */
|
|
50
|
+
code_challenge: string;
|
|
51
|
+
/** only `S256` is accepted */
|
|
52
|
+
code_challenge_method: 'S256';
|
|
53
|
+
/** the client's own correlation value, 1 to 256 characters of `A-Z a-z 0-9 . _ ~ -`, handed back with the outcome */
|
|
54
|
+
state?: string;
|
|
55
|
+
}
|
|
56
|
+
/** The JSON body of `POST <prefix>bearer/claim`. */
|
|
57
|
+
export interface IRelyingPartyBearerClaimRequest {
|
|
58
|
+
/** the hand-off code, claimed once */
|
|
59
|
+
code: string;
|
|
60
|
+
/** the verifier of the challenge the code is bound to (`isPkceVerifier`) */
|
|
61
|
+
codeVerifier: string;
|
|
62
|
+
}
|
|
63
|
+
/** The JSON body of `POST <prefix>bearer/refresh`. */
|
|
64
|
+
export interface IRelyingPartyBearerRefreshRequest {
|
|
65
|
+
/** the current refresh token; this call spends it */
|
|
66
|
+
refreshToken: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The JSON body of `POST <prefix>bearer/logout`. The session is named by
|
|
70
|
+
* `Authorization: Bearer <accessToken>`, or by `refreshToken` once that access
|
|
71
|
+
* token has expired.
|
|
72
|
+
*/
|
|
73
|
+
export interface IRelyingPartyBearerLogoutRequest {
|
|
74
|
+
refreshToken?: string;
|
|
75
|
+
}
|
|
39
76
|
/** The session a bearer answer names. */
|
|
40
77
|
export interface IRelyingPartyBearerSessionInfo {
|
|
41
78
|
sessionId: string;
|
|
@@ -6,4 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** The `type` of every message a popup delivery posts. */
|
|
8
8
|
export const RELYING_PARTY_BEARER_MESSAGE_TYPE = 'idp.global/relyingparty:bearer-handoff';
|
|
9
|
-
|
|
9
|
+
/** RFC 7636 §4.1: a code verifier is 43 to 128 characters of `A-Z a-z 0-9 . _ ~ -`. */
|
|
10
|
+
const PKCE_VERIFIER = /^[A-Za-z0-9._~-]{43,128}$/u;
|
|
11
|
+
/** Whether a value is a well-formed PKCE code verifier (RFC 7636 §4.1). */
|
|
12
|
+
export const isPkceVerifier = (valueArg) => typeof valueArg === 'string' && PKCE_VERIFIER.test(valueArg);
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udHJhY3QuYmVhcmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfcmVseWluZ3BhcnR5L2NvbnRyYWN0LmJlYXJlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7R0FLRztBQUVILDBEQUEwRDtBQUMxRCxNQUFNLENBQUMsTUFBTSxpQ0FBaUMsR0FBRyx3Q0FBd0MsQ0FBQztBQW1DMUYsdUZBQXVGO0FBQ3ZGLE1BQU0sYUFBYSxHQUFHLDRCQUE0QixDQUFDO0FBRW5ELDJFQUEyRTtBQUMzRSxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxRQUFpQixFQUFzQixFQUFFLENBQ3RFLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxhQUFhLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDIn0=
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { RELYING_PARTY_BEARER_MESSAGE_TYPE, type IRelyingPartyBearerMessage, type TRelyingPartyBearerClientKind } from './contract.bearer.js';
|
|
1
|
+
import { isPkceVerifier, RELYING_PARTY_BEARER_MESSAGE_TYPE, type IRelyingPartyBearerMessage, type TRelyingPartyBearerClientKind } from './contract.bearer.js';
|
|
2
2
|
import type { IRelyingPartyBearerDelivery, IRelyingPartyBearerReturnTargets } from './interfaces.js';
|
|
3
|
-
export { RELYING_PARTY_BEARER_MESSAGE_TYPE };
|
|
3
|
+
export { isPkceVerifier, RELYING_PARTY_BEARER_MESSAGE_TYPE };
|
|
4
4
|
export declare const RELYING_PARTY_BEARER_CLIENT_KINDS: readonly TRelyingPartyBearerClientKind[];
|
|
5
5
|
export declare const isBearerClientKind: (valueArg: unknown) => valueArg is TRelyingPartyBearerClientKind;
|
|
6
6
|
export declare const isPkceChallenge: (valueArg: unknown) => valueArg is string;
|
|
7
|
-
export declare const isPkceVerifier: (valueArg: unknown) => valueArg is string;
|
|
8
7
|
export declare const isBearerClientState: (valueArg: unknown) => valueArg is string;
|
|
9
8
|
/** The S256 challenge of a verifier: BASE64URL(SHA-256(ASCII(verifier))). */
|
|
10
9
|
export declare const pkceChallengeOf: (verifierArg: string) => string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { RELYING_PARTY_BEARER_MESSAGE_TYPE, } from './contract.bearer.js';
|
|
1
|
+
import { isPkceVerifier, RELYING_PARTY_BEARER_MESSAGE_TYPE, } from './contract.bearer.js';
|
|
2
2
|
import { sha256, timingSafeEqualString } from './helpers.crypto.js';
|
|
3
3
|
import { sanitizeReturnPath } from './helpers.returnpath.js';
|
|
4
|
-
|
|
4
|
+
// the verifier check lives in the import-free contract, which the browser client shares
|
|
5
|
+
export { isPkceVerifier, RELYING_PARTY_BEARER_MESSAGE_TYPE };
|
|
5
6
|
export const RELYING_PARTY_BEARER_CLIENT_KINDS = Object.freeze([
|
|
6
7
|
'web',
|
|
7
8
|
'loopback',
|
|
@@ -12,15 +13,12 @@ export const RELYING_PARTY_BEARER_CLIENT_KINDS = Object.freeze([
|
|
|
12
13
|
const MAX_RETURN_TARGET_LENGTH = 2_048;
|
|
13
14
|
/** BASE64URL of a SHA-256 digest, without padding (RFC 7636 §4.2). */
|
|
14
15
|
const PKCE_CHALLENGE = /^[A-Za-z0-9_-]{43}$/u;
|
|
15
|
-
/** RFC 7636 §4.1: 43 to 128 unreserved characters. */
|
|
16
|
-
const PKCE_VERIFIER = /^[A-Za-z0-9._~-]{43,128}$/u;
|
|
17
16
|
const CLIENT_STATE = /^[A-Za-z0-9._~-]{1,256}$/u;
|
|
18
17
|
const LOOPBACK_HOSTS = ['127.0.0.1', '[::1]'];
|
|
19
18
|
/** A private-use scheme is a reverse domain name: it has a dot, and no web scheme has one (RFC 8252 §7.1). */
|
|
20
19
|
const CUSTOM_SCHEME = /^[a-z][a-z0-9+-]*(\.[a-z0-9+-]+)+:$/u;
|
|
21
20
|
export const isBearerClientKind = (valueArg) => typeof valueArg === 'string' && RELYING_PARTY_BEARER_CLIENT_KINDS.includes(valueArg);
|
|
22
21
|
export const isPkceChallenge = (valueArg) => typeof valueArg === 'string' && PKCE_CHALLENGE.test(valueArg);
|
|
23
|
-
export const isPkceVerifier = (valueArg) => typeof valueArg === 'string' && PKCE_VERIFIER.test(valueArg);
|
|
24
22
|
export const isBearerClientState = (valueArg) => typeof valueArg === 'string' && CLIENT_STATE.test(valueArg);
|
|
25
23
|
/** The S256 challenge of a verifier: BASE64URL(SHA-256(ASCII(verifier))). */
|
|
26
24
|
export const pkceChallengeOf = (verifierArg) => sha256(verifierArg);
|
|
@@ -196,4 +194,4 @@ export const bearerPopupPage = (deliveryArg, outcomeArg, nonceArg) => {
|
|
|
196
194
|
};
|
|
197
195
|
/** The policy of a popup page: its nonce script and nothing else. */
|
|
198
196
|
export const bearerPopupContentSecurityPolicy = (nonceArg) => `default-src 'none'; script-src 'nonce-${nonceArg}'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'`;
|
|
199
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
197
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5iZWFyZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19yZWx5aW5ncGFydHkvaGVscGVycy5iZWFyZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLGNBQWMsRUFDZCxpQ0FBaUMsR0FHbEMsTUFBTSxzQkFBc0IsQ0FBQztBQUM5QixPQUFPLEVBQUUsTUFBTSxFQUFFLHFCQUFxQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDcEUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFHN0Qsd0ZBQXdGO0FBQ3hGLE9BQU8sRUFBRSxjQUFjLEVBQUUsaUNBQWlDLEVBQUUsQ0FBQztBQUU3RCxNQUFNLENBQUMsTUFBTSxpQ0FBaUMsR0FBNkMsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUN2RyxLQUFLO0lBQ0wsVUFBVTtJQUNWLGVBQWU7SUFDZixPQUFPO0NBQ1IsQ0FBQyxDQUFDO0FBRUgsMEZBQTBGO0FBQzFGLE1BQU0sd0JBQXdCLEdBQUcsS0FBSyxDQUFDO0FBQ3ZDLHNFQUFzRTtBQUN0RSxNQUFNLGNBQWMsR0FBRyxzQkFBc0IsQ0FBQztBQUM5QyxNQUFNLFlBQVksR0FBRywyQkFBMkIsQ0FBQztBQUNqRCxNQUFNLGNBQWMsR0FBc0IsQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDakUsOEdBQThHO0FBQzlHLE1BQU0sYUFBYSxHQUFHLHNDQUFzQyxDQUFDO0FBRTdELE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLENBQUMsUUFBaUIsRUFBNkMsRUFBRSxDQUNqRyxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUssaUNBQXVELENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBRTlHLE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxDQUFDLFFBQWlCLEVBQXNCLEVBQUUsQ0FDdkUsT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLGNBQWMsQ0FBQyxJQUFJLENB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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IOidcCompletion, TOidcCompletionFailureStage, TOidcPrompt, TOidcTokenHint } from './classes.idpoidcprovider.js';
|
|
2
2
|
import type { TRelyingPartyBearerClientKind } from './contract.bearer.js';
|
|
3
|
-
export type { IRelyingPartyBearerErrorAnswer, IRelyingPartyBearerLogoutAnswer, IRelyingPartyBearerMessage, IRelyingPartyBearerSessionAnswer, IRelyingPartyBearerSessionInfo, IRelyingPartyBearerTokenAnswer, TRelyingPartyBearerClientKind, TRelyingPartyBearerDeliveryError, TRelyingPartyBearerErrorCode, } from './contract.bearer.js';
|
|
3
|
+
export type { IRelyingPartyBearerClaimRequest, IRelyingPartyBearerErrorAnswer, IRelyingPartyBearerLoginQuery, IRelyingPartyBearerLogoutAnswer, IRelyingPartyBearerLogoutRequest, IRelyingPartyBearerMessage, IRelyingPartyBearerRefreshRequest, IRelyingPartyBearerSessionAnswer, IRelyingPartyBearerSessionInfo, IRelyingPartyBearerTokenAnswer, TRelyingPartyBearerClientKind, TRelyingPartyBearerDeliveryError, TRelyingPartyBearerErrorCode, } from './contract.bearer.js';
|
|
4
4
|
/** A session as the application reads it; no token and no sealed value is part of it. */
|
|
5
5
|
export interface IRelyingPartySession {
|
|
6
6
|
id: string;
|
|
@@ -129,6 +129,35 @@ export interface IRelyingPartyBearerHandoff {
|
|
|
129
129
|
/** epoch milliseconds, at most `BEARER_HANDOFF_MAX_AGE_SECONDS` after the callback */
|
|
130
130
|
expiresAt: number;
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* A bearer session the application opened for a device it paired
|
|
134
|
+
* (`openBearerHandoff`): the session, which has no provider login of its own
|
|
135
|
+
* (`idpSubject` and `loginAttemptId` are absent), and the single-use code the
|
|
136
|
+
* device claims at `bearer/claim` with the verifier of its challenge. The
|
|
137
|
+
* code is no token; only its hash is stored.
|
|
138
|
+
*/
|
|
139
|
+
export interface IRelyingPartyBearerHandoffOpening {
|
|
140
|
+
session: IRelyingPartySession;
|
|
141
|
+
code: string;
|
|
142
|
+
/** epoch milliseconds, at most `BEARER_HANDOFF_MAX_AGE_SECONDS` after the opening and never past the session */
|
|
143
|
+
expiresAt: number;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* What `eraseSessionsOfPrincipal` removed of one principal: the sessions it
|
|
147
|
+
* ended first, and the rows it deleted.
|
|
148
|
+
*/
|
|
149
|
+
export interface IRelyingPartySessionsErasure {
|
|
150
|
+
/** sessions not revoked yet (an expired one too) that it ended, each with `onSessionEnded` and its provider tokens handed back */
|
|
151
|
+
endedSessions: number;
|
|
152
|
+
/** session rows, live or ended */
|
|
153
|
+
deletedSessions: number;
|
|
154
|
+
/** the sign-in transactions the provider sessions came out of */
|
|
155
|
+
deletedLoginAttempts: number;
|
|
156
|
+
/** bearer hand-offs, claimed or not */
|
|
157
|
+
deletedBearerHandoffs: number;
|
|
158
|
+
/** bearer token generations, current or rotated */
|
|
159
|
+
deletedBearerTokens: number;
|
|
160
|
+
}
|
|
132
161
|
/**
|
|
133
162
|
* The relying party's own tokens of a bearer session. The refresh token
|
|
134
163
|
* rotates on every use; a rotated one coming back ends the session.
|
|
@@ -254,21 +283,31 @@ export interface IRelyingPartyHooks {
|
|
|
254
283
|
* A provider login opened a session, linked to its attempt. Runs before the
|
|
255
284
|
* login answers; a throw fails the login: the session is revoked, the
|
|
256
285
|
* provider tokens are handed back and the original error is rethrown.
|
|
286
|
+
* `delivery` is where a bearer login's outcome goes (its client kind, return
|
|
287
|
+
* target and the client's `state`); a cookie login has none.
|
|
257
288
|
*/
|
|
258
289
|
onProviderSessionOpened?(eventArg: {
|
|
259
290
|
session: IRelyingPartySession;
|
|
291
|
+
delivery?: IRelyingPartyBearerDelivery;
|
|
260
292
|
}): Promise<void>;
|
|
261
293
|
/**
|
|
262
294
|
* A session ended, or a caller tried to end one that was already over
|
|
263
295
|
* (`revokedHere: false`). Close its live connections when `revokedHere`,
|
|
264
296
|
* drop cached authority either way. Must not wait. A throw (or a rejected
|
|
265
297
|
* promise) is reported as a `hook_failed` event and never stops the end:
|
|
266
|
-
* the provider tokens go back regardless.
|
|
298
|
+
* the provider tokens go back regardless. An end that revoked the session
|
|
299
|
+
* here names its principal (`principalId`); one that found it over already
|
|
300
|
+
* does not.
|
|
267
301
|
*/
|
|
268
302
|
onSessionEnded?(eventArg: {
|
|
269
303
|
sessionId: string;
|
|
270
304
|
reason: TRelyingPartySessionEndReason;
|
|
271
|
-
revokedHere:
|
|
305
|
+
revokedHere: true;
|
|
306
|
+
principalId: string;
|
|
307
|
+
} | {
|
|
308
|
+
sessionId: string;
|
|
309
|
+
reason: TRelyingPartySessionEndReason;
|
|
310
|
+
revokedHere: false;
|
|
272
311
|
}): void;
|
|
273
312
|
/**
|
|
274
313
|
* Structured diagnostics; the default writes one JSON line to
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -152,7 +152,7 @@ await idp.logout();
|
|
|
152
152
|
| `getTransferTokenAndSwitchToLocation()` | Request a transfer token and navigate to the same configured target with `transfertoken` attached. |
|
|
153
153
|
| `processTransferToken()` | Consume a `transfertoken` from the current URL and refresh local auth state. |
|
|
154
154
|
| `logout()` | Revoke and clear the current login flow. |
|
|
155
|
-
| `RelyingPartyBearerClient` | The bearer hand-off client of a relying party's own `web` and `popup` logins: PKCE start URL, the redirect and popup receivers, claim, refresh single-flight across tabs through Web Locks, and logout, with the token pair in an application storage adapter (`IRelyingPartyBearerTokenStorage`). See [Bearer Hand-off](#bearer-hand-off). |
|
|
155
|
+
| `RelyingPartyBearerClient` | The bearer hand-off client of a relying party's own `web` and `popup` logins: PKCE start URL, the redirect and popup receivers, claim, refresh single-flight across tabs through Web Locks, and logout, with the token pair in an application storage adapter (`IRelyingPartyBearerTokenStorage`). `createPkce()` and `claimCode(pkce, code)` claim a code delivered another way, such as a device pairing, without a window. See [Bearer Hand-off](#bearer-hand-off). |
|
|
156
156
|
|
|
157
157
|
### Typed Request Shortcuts
|
|
158
158
|
|
|
@@ -614,6 +614,13 @@ What it does:
|
|
|
614
614
|
Nothing runs on a timer, and no token, cookie or sealed value ever reaches a
|
|
615
615
|
log line or an error message.
|
|
616
616
|
|
|
617
|
+
`onSessionEnded` names the session, the reason and whether this call revoked
|
|
618
|
+
it (`revokedHere`); when it did, it also names the session's principal
|
|
619
|
+
(`principalId`), and when the session was over already it does not.
|
|
620
|
+
`onProviderSessionOpened` receives the session and, for a bearer login, its
|
|
621
|
+
`delivery`: the client kind, the return target and the client's `state`. A
|
|
622
|
+
cookie login has no `delivery`.
|
|
623
|
+
|
|
617
624
|
Application hooks never stand in the way of that cleanup. A throwing (or
|
|
618
625
|
rejecting) `onSessionEnded` or `onEvent` is reported as a `hook_failed` event —
|
|
619
626
|
`onEvent`'s own failure on `console.error` — and the provider tokens go back
|
|
@@ -648,9 +655,46 @@ example after a local password check.
|
|
|
648
655
|
|
|
649
656
|
Nothing runs on a timer. A login start first removes up to 100 login attempts
|
|
650
657
|
and up to 100 sessions that ended more than an hour ago, and a bearer login
|
|
651
|
-
start also its bearer hand-offs and token generations; `
|
|
652
|
-
the
|
|
653
|
-
provider.
|
|
658
|
+
start also its bearer hand-offs and token generations; `openBearerHandoff`
|
|
659
|
+
removes the bearer state and the sessions, `openSession` the sessions only.
|
|
660
|
+
Removing an ended session fires no end hook and calls no provider.
|
|
661
|
+
|
|
662
|
+
**Deleting an account.** The sweeps above run only when a session is created,
|
|
663
|
+
so an ended session can stay stored for long on a quiet service.
|
|
664
|
+
`eraseSessionsOfPrincipal(principalId)` removes everything of one principal at
|
|
665
|
+
once: an application calls it when it deletes the account, after its own
|
|
666
|
+
erasers and before its user record, so a failure can be retried.
|
|
667
|
+
|
|
668
|
+
```ts
|
|
669
|
+
await accountData.eraseAll(userId); // the application's own records
|
|
670
|
+
const erasure = await sessions.eraseSessionsOfPrincipal(userId);
|
|
671
|
+
await users.delete(userId);
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
- It ends every session of the principal that is not revoked yet, an expired
|
|
675
|
+
one too, as `endSession` does: its tokens stop working at once,
|
|
676
|
+
`onSessionEnded` fires (reason `logout`, with `principalId`), and its
|
|
677
|
+
provider tokens go back to idp.global, which a deleted row could no longer
|
|
678
|
+
do.
|
|
679
|
+
- Then it deletes every session row of the principal, live or ended, and with
|
|
680
|
+
each the login attempt a provider session came out of and, in bearer mode,
|
|
681
|
+
its hand-offs (claimed or not) and its token generations. The session row
|
|
682
|
+
goes last: it is the only link from the principal to the others, so an
|
|
683
|
+
erase that fails half way leaves a retry everything it needs.
|
|
684
|
+
- It answers what it removed (`IRelyingPartySessionsErasure`: `endedSessions`,
|
|
685
|
+
`deletedSessions`, `deletedLoginAttempts`, `deletedBearerHandoffs`,
|
|
686
|
+
`deletedBearerTokens`); a second call finds nothing.
|
|
687
|
+
- A session opened by a login already past `admit` when the erase runs is not
|
|
688
|
+
covered. It keeps the principal id until it expires and a later sweep
|
|
689
|
+
removes it. A second `eraseSessionsOfPrincipal(principalId)`, run after the
|
|
690
|
+
user record is deleted, removes it. A token generation issued by a refresh
|
|
691
|
+
already under way is not covered either; it outlives its session row as
|
|
692
|
+
described below.
|
|
693
|
+
- A token generation, and for at most a minute a hand-off, outlives the row
|
|
694
|
+
of its session when that session was revoked and swept before its expiry.
|
|
695
|
+
It holds only hashes, the session id and times, the bearer sweep removes it
|
|
696
|
+
an hour after the session would have expired, and the erase cannot
|
|
697
|
+
attribute it to the principal any more.
|
|
654
698
|
|
|
655
699
|
**Building before the database connects.** `db` may be a function that answers
|
|
656
700
|
the database once it exists. It is called on every use of a model, the way
|
|
@@ -843,8 +887,10 @@ Tokens and their ends:
|
|
|
843
887
|
bearer login sweeps it (`handoff_unclaimed`). Each answers 400
|
|
844
888
|
`invalid_bearer_handoff`; reuse is also reported as a
|
|
845
889
|
`bearer_credential_reused` event.
|
|
846
|
-
- A bearer
|
|
847
|
-
`endSessionsOfPrincipal` and `AuthorityObserver` apply as they are. A
|
|
890
|
+
- A bearer login is a provider login like a cookie session: `onSessionEnded`,
|
|
891
|
+
`endSessionsOfPrincipal` and `AuthorityObserver` apply as they are. (A
|
|
892
|
+
session opened by pairing a device has no provider login of its own; see
|
|
893
|
+
"Pairing a device" below.) A refresh
|
|
848
894
|
rotates the provider token when it is due and ends the session when
|
|
849
895
|
idp.global has ended the login (`invalid_bearer_grant`); an unreachable
|
|
850
896
|
provider answers 503 `authority_unavailable` and spends nothing. Until then
|
|
@@ -877,8 +923,13 @@ The client side:
|
|
|
877
923
|
The HTTP answers are exported as types (`IRelyingPartyBearerTokenAnswer`,
|
|
878
924
|
`IRelyingPartyBearerSessionAnswer`, `IRelyingPartyBearerLogoutAnswer`,
|
|
879
925
|
`IRelyingPartyBearerErrorAnswer` with `TRelyingPartyBearerErrorCode`, and the
|
|
880
|
-
popup's `IRelyingPartyBearerMessage`)
|
|
881
|
-
|
|
926
|
+
popup's `IRelyingPartyBearerMessage`), and so are the requests:
|
|
927
|
+
`IRelyingPartyBearerLoginQuery` (the `bearer/login` query by its parameter
|
|
928
|
+
names), `IRelyingPartyBearerClaimRequest` (`{ code, codeVerifier }`),
|
|
929
|
+
`IRelyingPartyBearerRefreshRequest` (`{ refreshToken }`) and
|
|
930
|
+
`IRelyingPartyBearerLogoutRequest` (`{ refreshToken? }`). They come from a
|
|
931
|
+
module that imports nothing, so `@idp.global/sdk/browser` and
|
|
932
|
+
`@idp.global/sdk/browser/bearer` share them with the server.
|
|
882
933
|
|
|
883
934
|
**The browser client.** `RelyingPartyBearerClient` from `@idp.global/sdk/browser`
|
|
884
935
|
is that client side for `web` and `popup` logins. The application owns where the
|
|
@@ -929,6 +980,16 @@ await client.logout();
|
|
|
929
980
|
`previous_session_not_revoked` with `previousSessionError`. In the last case
|
|
930
981
|
that session lives on at the relying party until it expires. A failed claim
|
|
931
982
|
leaves the stored pair and its session as they were.
|
|
983
|
+
- `createPkce()` makes a verifier and its S256 challenge from 32 random bytes,
|
|
984
|
+
and `claimCode(pkce, code)` claims a code bound to that challenge which
|
|
985
|
+
reached the client another way than its own login, such as a device pairing
|
|
986
|
+
("Pairing a device" below). It is received like a login: one
|
|
987
|
+
claim, never retried, under `requestTimeoutMs`, and the pair replaces the
|
|
988
|
+
stored one under the lock (`IRelyingPartyBearerLoginResult`). A pair is
|
|
989
|
+
claimed with once: a second call with it, after a failed claim as well, is
|
|
990
|
+
`already_received` and sends nothing. A pair that is not one (a malformed
|
|
991
|
+
verifier, a challenge that is not its S256) or a code that is not text
|
|
992
|
+
throws before anything is sent. Neither needs a window.
|
|
932
993
|
- `openPopupLogin(login, { timeoutMs?, signal?, windowFeatures? })` calls
|
|
933
994
|
`window.open` before it returns, so a browser lets it through within the
|
|
934
995
|
click. It takes only a message from the relying party's exact origin, from
|
|
@@ -981,6 +1042,87 @@ await client.logout();
|
|
|
981
1042
|
- The client needs the Web Locks API and Web Crypto, which a browser offers
|
|
982
1043
|
only in a secure context, and has no fallback for either. Every request is
|
|
983
1044
|
sent without credentials, cache or redirects.
|
|
1045
|
+
- It runs in a Manifest V3 extension's service worker. That was checked in
|
|
1046
|
+
Chromium 149: a module service worker imported `@idp.global/sdk/browser/bearer`
|
|
1047
|
+
and ran `createPkce`, `claimCode` against the bearer routes and a rotation
|
|
1048
|
+
through `accessToken()`, with the lock held at each write. Other browsers'
|
|
1049
|
+
extension workers were not checked. The extension needs a host permission
|
|
1050
|
+
for the relying party's origin: the routes answer CORS for popup origins
|
|
1051
|
+
only, so without one the browser's preflight is refused and the claim fails
|
|
1052
|
+
with `request_failed` before it is sent.
|
|
1053
|
+
|
|
1054
|
+
**Pairing a device.** A person who is signed in can sign a second device of
|
|
1055
|
+
theirs in without a login on it, such as a TV or a browser extension. The
|
|
1056
|
+
device makes a PKCE pair and shows the application its challenge, for example
|
|
1057
|
+
together with a short code that the person confirms in a session where they
|
|
1058
|
+
are signed in. The application's server then opens the device's session from
|
|
1059
|
+
the session that confirmed:
|
|
1060
|
+
|
|
1061
|
+
```ts
|
|
1062
|
+
// the device polls; the person has confirmed the pairing in one of their sessions
|
|
1063
|
+
const { code, expiresAt } = await sessions.openBearerHandoff({
|
|
1064
|
+
openerSessionId: pairing.confirmedInSessionId, // written by the confirm handler, never taken from the device
|
|
1065
|
+
codeChallenge: pairing.codeChallenge, // the device's S256 challenge
|
|
1066
|
+
maxAgeSeconds: 8 * 60 * 60, // optional: 1 to sessionMaxAgeSeconds, which is the default
|
|
1067
|
+
});
|
|
1068
|
+
// answer the poll with `code`; the device claims it at POST /auth/bearer/claim with its verifier
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
```ts
|
|
1072
|
+
// the device, with the browser client (a native client claims at bearer/claim itself)
|
|
1073
|
+
const pkce = await client.createPkce();
|
|
1074
|
+
await showPairingRequest(pkce.codeChallenge);
|
|
1075
|
+
const result = await client.claimCode(pkce, await pollForCode());
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
- **The opener.** `openerSessionId` must name a live provider login: not ended
|
|
1079
|
+
and not expired, with an active principal and a provider login of its own
|
|
1080
|
+
(`idpSubject`). A session from `openSession` or from `openBearerHandoff`
|
|
1081
|
+
does not qualify, so a paired device cannot pair another one. Any other
|
|
1082
|
+
opener throws `RelyingPartyError('invalid_handoff_opener')` (400). A
|
|
1083
|
+
challenge that is not one throws `invalid_bearer_request`, and a
|
|
1084
|
+
`maxAgeSeconds` outside 1 to `sessionMaxAgeSeconds` throws a `RangeError`.
|
|
1085
|
+
Nothing is stored for any of these. Without `bearer` the call throws
|
|
1086
|
+
`login_method_unavailable`; in bearer-only mode it works, with a bearer login
|
|
1087
|
+
as the opener. The check reads this relying party's state of the opener;
|
|
1088
|
+
observe authority with the opener first to ask idp.global itself.
|
|
1089
|
+
- **The pairing record.** It keeps the id of the session that confirmed,
|
|
1090
|
+
written by the confirm handler and never taken from the device. That opener
|
|
1091
|
+
must still be live when the poll delivers the code. Open one hand-off per
|
|
1092
|
+
confirmed pairing, in the poll that delivers it; then the code is never
|
|
1093
|
+
stored, and only the public challenge sits in the record. Only server code
|
|
1094
|
+
opens a hand-off, and no route does. Guard the confirmation against a code
|
|
1095
|
+
the person did not start themselves (RFC 8628 §5.4).
|
|
1096
|
+
- **The code** is a bearer login's code: one claim within
|
|
1097
|
+
`BEARER_HANDOFF_MAX_AGE_SECONDS` (60) and never past the session, bound to
|
|
1098
|
+
the challenge, stored only as a hash. A claimed code that comes back, a wrong
|
|
1099
|
+
verifier or a code nobody claims ends the session.
|
|
1100
|
+
- **The session** acts as the opener's principal and has no `idpSubject` or
|
|
1101
|
+
`loginAttemptId`. It lives `maxAgeSeconds` from the opening, the same rule as
|
|
1102
|
+
every session, and no refresh extends it. It refreshes without asking the
|
|
1103
|
+
provider. Its expiry, `endSession`, `endSessionsOfPrincipal`, the device's
|
|
1104
|
+
`bearer/logout` and the reuse of its code or refresh token end it; an
|
|
1105
|
+
inactive principal resolves it to nothing.
|
|
1106
|
+
- **What does not reach it.** It holds no provider token, so an idp.global
|
|
1107
|
+
logout or a change of the person's memberships never reaches it, and
|
|
1108
|
+
`AuthorityObserver.observe` refuses it (`authority_login_required`) without
|
|
1109
|
+
ending it. So the application:
|
|
1110
|
+
- grants a paired session (`session.idpSubject` absent) no idp.global
|
|
1111
|
+
organization authority and does not observe it. An application that ends
|
|
1112
|
+
sessions on `authority_login_required` must not observe paired sessions at
|
|
1113
|
+
all;
|
|
1114
|
+
- sees idp.global end a login only as `onSessionEnded` with the reason
|
|
1115
|
+
`refresh_token_refused` for one of the person's provider sessions. It may
|
|
1116
|
+
answer that by ending the person's paired sessions: `endSession` for those
|
|
1117
|
+
`sessionsOfPrincipal` lists without `idpSubject`, or
|
|
1118
|
+
`endSessionsOfPrincipal` to end every session of the person. The hook
|
|
1119
|
+
names the principal of a session it revoked (`principalId`), so the
|
|
1120
|
+
application needs no table of sessions and principals of its own;
|
|
1121
|
+
- ends paired sessions itself when the person signs out everywhere, and
|
|
1122
|
+
erases them with every other session when the account goes
|
|
1123
|
+
(`eraseSessionsOfPrincipal`, see "Deleting an account");
|
|
1124
|
+
- chooses `sessionMaxAgeSeconds`, or `maxAgeSeconds` per pairing, knowing
|
|
1125
|
+
that a paired device lives that long without a check at idp.global.
|
|
984
1126
|
|
|
985
1127
|
**Bearer-only mode.** An application whose every client is a bearer client turns
|
|
986
1128
|
the cookie login off with `cookieSessions: false`. No route then reads or sets a
|
|
@@ -1004,7 +1146,8 @@ const sessions = new RelyingPartySessions({
|
|
|
1004
1146
|
`null`, like every path the routes do not serve. `startLogin`,
|
|
1005
1147
|
`completeLogin`, `abandonLogin`, `finalizeLogin`, `openSession`,
|
|
1006
1148
|
`clearSessionCookie` and `clearIntentCookie` throw `login_method_unavailable`,
|
|
1007
|
-
and `resolveCookieSession` answers `null`.
|
|
1149
|
+
and `resolveCookieSession` answers `null`. `openBearerHandoff` works, with a
|
|
1150
|
+
bearer login as the opener. A callback that belongs to no bearer
|
|
1008
1151
|
login redirects to `failureLocation` when the provider sent an `error`, and
|
|
1009
1152
|
otherwise answers 400 `authentication_failed`. `RelyingPartySocketBinding`
|
|
1010
1153
|
registers a browser by its cookie session, so it binds no connection in this
|
|
@@ -1088,7 +1231,7 @@ const delivery = await readBearerDelivery(await fetch(callback, { redirect: 'man
|
|
|
1088
1231
|
| --- | --- |
|
|
1089
1232
|
| `IdpOidcProvider` | Discovery, PKCE authorization URL, code exchange with UserInfo, refresh, revocation and organization introspection for idp.global. Implements `IOidcProvider`. `trustedCaPem` or `fetch` for an issuer under a private CA. |
|
|
1090
1233
|
| `createRelyingPartyModels()` | Binds the session, login-attempt, bearer hand-off and bearer token models to one `SmartdataDb`, or to a function asked for it on every use, optionally under existing collection names. |
|
|
1091
|
-
| `RelyingPartySessions<TPrincipal>` | Login start (with an application intent and a prompt)/complete/abandon/finalize, `openSession` (with a shorter lifetime), `resolveCookieSession`, `revalidateSession`, `revalidateLiveSession`, `isSessionLive`, `validateCsrf`, `logout`/`endSession`, `endSessionsOfPrincipal`, `sessionsOfPrincipal` (paged), `liveProviderToken` (with `rejectedAccessToken`). Principals give `isActive` or `resolve` (`IRelyingPartyPrincipals`, `IRelyingPartyPrincipalResolver<TPrincipal>`); with `resolve`, live sessions carry `principal`. With `bearer`: `startBearerLogin`, `bearerDeliveryOf`, `completeBearerLogin`, `abandonBearerLogin`, `claimBearerHandoff`, `refreshBearerTokens`, `resolveBearerAccessToken`, `resolveBearerSession`, `endBearerSession`; `cookieSessions: false` (`IRelyingPartyBearerOnlySessionsOptions`) runs bearer hand-off alone, without cookies or a CSRF key. |
|
|
1234
|
+
| `RelyingPartySessions<TPrincipal>` | Login start (with an application intent and a prompt)/complete/abandon/finalize, `openSession` (with a shorter lifetime), `resolveCookieSession`, `revalidateSession`, `revalidateLiveSession`, `isSessionLive`, `validateCsrf`, `logout`/`endSession`, `endSessionsOfPrincipal`, `eraseSessionsOfPrincipal` (deleting an account), `sessionsOfPrincipal` (paged), `liveProviderToken` (with `rejectedAccessToken`). Principals give `isActive` or `resolve` (`IRelyingPartyPrincipals`, `IRelyingPartyPrincipalResolver<TPrincipal>`); with `resolve`, live sessions carry `principal`. With `bearer`: `startBearerLogin`, `bearerDeliveryOf`, `completeBearerLogin`, `abandonBearerLogin`, `openBearerHandoff` (pairing a device from a live provider login), `claimBearerHandoff`, `refreshBearerTokens`, `resolveBearerAccessToken`, `resolveBearerSession`, `endBearerSession`; `cookieSessions: false` (`IRelyingPartyBearerOnlySessionsOptions`) runs bearer hand-off alone, without cookies or a CSRF key. |
|
|
1092
1235
|
| `AuthorityObserver`, `AUTHORITY_FORCED_REFRESH_INTERVAL_MS` | Live organization authority with opt-in positive and negative reuse. An inactive answer rotates the session's token, at most once per interval, before it refuses. |
|
|
1093
1236
|
| `RelyingPartyRoutes` | `handle(ctx)` for `/auth/login`, `/auth/callback`, `/auth/finalize`, `/auth/session` and `/auth/logout`; `null` for anything else. Takes a typedserver route context. `onLogout` is awaited after a CSRF-checked logout ended the session. In bearer mode also `/auth/bearer/login`, `/auth/bearer/claim`, `/auth/bearer/refresh`, `/auth/bearer/session` and `/auth/bearer/logout`, with CORS for the popup origins only; in bearer-only mode `/auth/login`, `/auth/finalize`, `/auth/session` and `/auth/logout` answer `null`. |
|
|
1094
1237
|
| `createActorScope()` | AsyncLocalStorage actor context with a TypedRouter request scope. |
|
|
@@ -1096,6 +1239,9 @@ const delivery = await readBearerDelivery(await fetch(callback, { redirect: 'man
|
|
|
1096
1239
|
| `SecretBox`, `deriveCsrfToken()`, `decodeSecretKey()`, `parseCookies()`, `serializeCookie()`, `sanitizeReturnPath()` | The primitives underneath. |
|
|
1097
1240
|
| `pkceChallengeOf()`, `verifyPkceChallenge()`, `parseBearerAuthorization()`, `isBearerReturnTargetAllowed()`, `RELYING_PARTY_BEARER_MESSAGE_TYPE`, `BEARER_HANDOFF_MAX_AGE_SECONDS` | The bearer primitives: S256, the `Authorization` header, the return-target match and the popup message type. |
|
|
1098
1241
|
| `IRelyingPartyBearerTokenAnswer`, `IRelyingPartyBearerSessionAnswer`, `IRelyingPartyBearerLogoutAnswer`, `IRelyingPartyBearerErrorAnswer`, `TRelyingPartyBearerErrorCode`, `IRelyingPartyBearerMessage` | The HTTP answers of the bearer routes and the popup message, shared with `@idp.global/sdk/browser`. |
|
|
1242
|
+
| `IRelyingPartyBearerLoginQuery`, `IRelyingPartyBearerClaimRequest`, `IRelyingPartyBearerRefreshRequest`, `IRelyingPartyBearerLogoutRequest` | The requests of the bearer routes: the `bearer/login` query and the JSON bodies, shared with `@idp.global/sdk/browser`. |
|
|
1243
|
+
| `IRelyingPartyBearerHandoffOpening` | What `openBearerHandoff` answers: the paired session, its code and when the code expires. |
|
|
1244
|
+
| `IRelyingPartySessionsErasure` | What `eraseSessionsOfPrincipal` answers: how many sessions it ended, and how many sessions, login attempts, bearer hand-offs and token generations it deleted. |
|
|
1099
1245
|
| `RelyingPartyError`, `RelyingPartyRefusal`, `LoginCallbackError`, `AuthorityTokenError`, `AuthorityRefusedError`, `RelyingPartyDatabaseUnavailableError` | Errors with stable codes; none carries token material. A `RelyingPartyRefusal` may name the application's `location` for the browser. |
|
|
1100
1246
|
| `RelyingPartyProviderDouble` (`/relyingparty/testing`) | An in-process idp.global for an application's tests: personas, live organization roles, single-use codes, rotating and reuse-detecting refresh tokens, recorded calls and injectable failures. `createBearerPkcePair()` and `readBearerDelivery()` for bearer clients. |
|
|
1101
1247
|
|