@idp.global/sdk 19.3.0 → 19.4.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 +40 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_browser/classes.idpclient.js +1 -1
- package/dist_ts_browser/classes.idprequests.js +1 -1
- package/dist_ts_browser/classes.relyingpartybearerclient.d.ts +276 -0
- package/dist_ts_browser/classes.relyingpartybearerclient.js +591 -0
- package/dist_ts_browser/index.d.ts +1 -0
- package/dist_ts_browser/index.js +2 -1
- package/dist_ts_browser/plugins.js +1 -1
- package/dist_ts_browser/secret-delivery.js +1 -1
- package/dist_ts_relyingparty/classes.authorityobserver.d.ts +23 -0
- package/dist_ts_relyingparty/classes.authorityobserver.js +68 -13
- package/dist_ts_relyingparty/classes.errors.d.ts +3 -1
- package/dist_ts_relyingparty/classes.errors.js +2 -1
- package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +12 -0
- package/dist_ts_relyingparty/classes.relyingpartyroutes.js +33 -6
- package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +102 -7
- package/dist_ts_relyingparty/classes.relyingpartysessions.js +228 -29
- package/dist_ts_relyingparty/classes.rpmodels.d.ts +6 -4
- package/dist_ts_relyingparty/classes.rpmodels.js +8 -6
- package/dist_ts_relyingparty/contract.bearer.d.ts +82 -0
- package/dist_ts_relyingparty/contract.bearer.js +9 -0
- package/dist_ts_relyingparty/helpers.bearer.d.ts +3 -3
- package/dist_ts_relyingparty/helpers.bearer.js +3 -3
- package/dist_ts_relyingparty/interfaces.d.ts +34 -24
- package/package.json +2 -1
- package/readme.md +215 -19
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_browser/classes.relyingpartybearerclient.ts +821 -0
- package/ts_browser/index.ts +1 -0
- package/ts_relyingparty/classes.authorityobserver.ts +80 -12
- package/ts_relyingparty/classes.errors.ts +4 -1
- package/ts_relyingparty/classes.relyingpartyroutes.ts +38 -7
- package/ts_relyingparty/classes.relyingpartysessions.ts +298 -34
- package/ts_relyingparty/classes.rpmodels.ts +7 -4
- package/ts_relyingparty/contract.bearer.ts +99 -0
- package/ts_relyingparty/helpers.bearer.ts +8 -9
- package/ts_relyingparty/interfaces.ts +49 -26
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire contract of the bearer hand-off routes: what a bearer client sends
|
|
3
|
+
* and what it is answered. This module imports nothing, so the browser client
|
|
4
|
+
* (`@idp.global/sdk/browser`) shares it with the routes without reaching any
|
|
5
|
+
* server code; keep it that way.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The `type` of every message a popup delivery posts. */
|
|
9
|
+
export const RELYING_PARTY_BEARER_MESSAGE_TYPE = 'idp.global/relyingparty:bearer-handoff';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How a bearer client receives the outcome of its login:
|
|
13
|
+
*
|
|
14
|
+
* - `web`: a redirect to a same-origin path of the application
|
|
15
|
+
* - `loopback`: a redirect to `http://127.0.0.1:<port>/…` or `http://[::1]:<port>/…`, any port (RFC 8252)
|
|
16
|
+
* - `custom_scheme`: a redirect to a registered private-use URI scheme such as `com.example.app:/signed-in` (RFC 8252)
|
|
17
|
+
* - `popup`: a page that posts the outcome to `window.opener` at exactly one registered origin
|
|
18
|
+
*/
|
|
19
|
+
export type TRelyingPartyBearerClientKind = 'web' | 'loopback' | 'custom_scheme' | 'popup';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What the popup page of a `popup` delivery posts to `window.opener`. The
|
|
23
|
+
* receiver accepts it only when `event.origin` is the relying party's public
|
|
24
|
+
* origin, `event.source` is the popup it opened, `type` is
|
|
25
|
+
* `RELYING_PARTY_BEARER_MESSAGE_TYPE` and `state` is its own.
|
|
26
|
+
*/
|
|
27
|
+
export interface IRelyingPartyBearerMessage {
|
|
28
|
+
type: string;
|
|
29
|
+
/** the hand-off code; absent when the login failed */
|
|
30
|
+
code?: string;
|
|
31
|
+
/** why the login failed (`TRelyingPartyBearerDeliveryError`); absent when it succeeded */
|
|
32
|
+
error?: string;
|
|
33
|
+
state?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Why a bearer login delivered no code, as the `error` of its redirect or
|
|
38
|
+
* popup message: `access_denied` when the provider came back without one,
|
|
39
|
+
* `authentication_failed` when the login could not be completed, or the code
|
|
40
|
+
* of the application's own refusal (`RelyingPartyRefusal.code`).
|
|
41
|
+
*/
|
|
42
|
+
export type TRelyingPartyBearerDeliveryError = 'access_denied' | 'authentication_failed' | (string & {});
|
|
43
|
+
|
|
44
|
+
/** The session a bearer answer names. */
|
|
45
|
+
export interface IRelyingPartyBearerSessionInfo {
|
|
46
|
+
sessionId: string;
|
|
47
|
+
principalId: string;
|
|
48
|
+
/** epoch milliseconds: the end of the session, which no refresh extends */
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What `POST <prefix>bearer/claim` and `POST <prefix>bearer/refresh` answer with status 200. */
|
|
53
|
+
export interface IRelyingPartyBearerTokenAnswer {
|
|
54
|
+
tokenType: 'Bearer';
|
|
55
|
+
accessToken: string;
|
|
56
|
+
/** epoch milliseconds on the relying party's clock */
|
|
57
|
+
accessTokenExpiresAt: number;
|
|
58
|
+
/** seconds the access token lives from the answer, for a client whose clock differs */
|
|
59
|
+
expiresIn: number;
|
|
60
|
+
/** spent by the next refresh; it never comes back after that */
|
|
61
|
+
refreshToken: string;
|
|
62
|
+
/** epoch milliseconds: the end of the session */
|
|
63
|
+
refreshTokenExpiresAt: number;
|
|
64
|
+
session: IRelyingPartyBearerSessionInfo;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** What `GET <prefix>bearer/session` answers with status 200; `null` for any token that is not live. */
|
|
68
|
+
export interface IRelyingPartyBearerSessionAnswer {
|
|
69
|
+
session: IRelyingPartyBearerSessionInfo | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What `POST <prefix>bearer/logout` answers with status 200; `true` only when this call ended a live session. */
|
|
73
|
+
export interface IRelyingPartyBearerLogoutAnswer {
|
|
74
|
+
endedLiveSession: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The `error` of a failed bearer route, answered as `{ error }`:
|
|
79
|
+
*
|
|
80
|
+
* - `invalid_bearer_request` (400): a login start or claim with a missing or malformed parameter
|
|
81
|
+
* - `return_target_not_allowed` (400): a login start naming a return target not configured for its kind
|
|
82
|
+
* - `invalid_bearer_handoff` (400): a claim with a code that is unknown, expired, already claimed or of the wrong verifier
|
|
83
|
+
* - `invalid_bearer_grant` (400): a refresh token that is unknown, spent, expired or of a session that is over
|
|
84
|
+
* - `authority_unavailable` (503): a refresh that could not reach the provider; nothing was spent, retry it
|
|
85
|
+
* - `authentication_unavailable` (503): any other failure on the relying party's side
|
|
86
|
+
* - `forbidden` (403): a CORS preflight from an origin that is no popup return target
|
|
87
|
+
*/
|
|
88
|
+
export type TRelyingPartyBearerErrorCode =
|
|
89
|
+
| 'invalid_bearer_request'
|
|
90
|
+
| 'return_target_not_allowed'
|
|
91
|
+
| 'invalid_bearer_handoff'
|
|
92
|
+
| 'invalid_bearer_grant'
|
|
93
|
+
| 'authority_unavailable'
|
|
94
|
+
| 'authentication_unavailable'
|
|
95
|
+
| 'forbidden';
|
|
96
|
+
|
|
97
|
+
export interface IRelyingPartyBearerErrorAnswer {
|
|
98
|
+
error: TRelyingPartyBearerErrorCode;
|
|
99
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RELYING_PARTY_BEARER_MESSAGE_TYPE,
|
|
3
|
+
type IRelyingPartyBearerMessage,
|
|
4
|
+
type TRelyingPartyBearerClientKind,
|
|
5
|
+
} from './contract.bearer.js';
|
|
1
6
|
import { sha256, timingSafeEqualString } from './helpers.crypto.js';
|
|
2
7
|
import { sanitizeReturnPath } from './helpers.returnpath.js';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
IRelyingPartyBearerReturnTargets,
|
|
7
|
-
TRelyingPartyBearerClientKind,
|
|
8
|
-
} from './interfaces.js';
|
|
8
|
+
import type { IRelyingPartyBearerDelivery, IRelyingPartyBearerReturnTargets } from './interfaces.js';
|
|
9
|
+
|
|
10
|
+
export { RELYING_PARTY_BEARER_MESSAGE_TYPE };
|
|
9
11
|
|
|
10
12
|
export const RELYING_PARTY_BEARER_CLIENT_KINDS: readonly TRelyingPartyBearerClientKind[] = Object.freeze([
|
|
11
13
|
'web',
|
|
@@ -14,9 +16,6 @@ export const RELYING_PARTY_BEARER_CLIENT_KINDS: readonly TRelyingPartyBearerClie
|
|
|
14
16
|
'popup',
|
|
15
17
|
]);
|
|
16
18
|
|
|
17
|
-
/** The `type` of every message a popup delivery posts. */
|
|
18
|
-
export const RELYING_PARTY_BEARER_MESSAGE_TYPE = 'idp.global/relyingparty:bearer-handoff';
|
|
19
|
-
|
|
20
19
|
/** A return target is a location the application configured, not a place to park data. */
|
|
21
20
|
const MAX_RETURN_TARGET_LENGTH = 2_048;
|
|
22
21
|
/** BASE64URL of a SHA-256 digest, without padding (RFC 7636 §4.2). */
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { IOidcCompletion, TOidcCompletionFailureStage, TOidcPrompt, TOidcTokenHint } from './classes.idpoidcprovider.js';
|
|
2
|
+
import type { TRelyingPartyBearerClientKind } from './contract.bearer.js';
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
IRelyingPartyBearerErrorAnswer,
|
|
6
|
+
IRelyingPartyBearerLogoutAnswer,
|
|
7
|
+
IRelyingPartyBearerMessage,
|
|
8
|
+
IRelyingPartyBearerSessionAnswer,
|
|
9
|
+
IRelyingPartyBearerSessionInfo,
|
|
10
|
+
IRelyingPartyBearerTokenAnswer,
|
|
11
|
+
TRelyingPartyBearerClientKind,
|
|
12
|
+
TRelyingPartyBearerDeliveryError,
|
|
13
|
+
TRelyingPartyBearerErrorCode,
|
|
14
|
+
} from './contract.bearer.js';
|
|
2
15
|
|
|
3
16
|
/** A session as the application reads it; no token and no sealed value is part of it. */
|
|
4
17
|
export interface IRelyingPartySession {
|
|
@@ -35,6 +48,21 @@ export interface IRelyingPartySessionOpening {
|
|
|
35
48
|
sessionCookie: string;
|
|
36
49
|
}
|
|
37
50
|
|
|
51
|
+
/** What `sessionsOfPrincipal` reads: how many sessions, and after which page. */
|
|
52
|
+
export interface IRelyingPartySessionsPageOptions {
|
|
53
|
+
/** 1 to `RELYING_PARTY_MAX_SESSIONS_PAGE_LIMIT`; `RELYING_PARTY_DEFAULT_SESSIONS_PAGE_LIMIT` when absent */
|
|
54
|
+
limit?: number;
|
|
55
|
+
/** the `nextCursor` of the page before; absent for the first page */
|
|
56
|
+
cursor?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** One page of a principal's live sessions, newest first. */
|
|
60
|
+
export interface IRelyingPartySessionsPage {
|
|
61
|
+
sessions: IRelyingPartySession[];
|
|
62
|
+
/** opaque; reads the next page as `cursor`. Absent on the last page. */
|
|
63
|
+
nextCursor?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
/**
|
|
39
67
|
* What `startLogin` carries besides the return path. `intent` is the
|
|
40
68
|
* application's own text — the invitation a login accepts, the account it
|
|
@@ -62,16 +90,6 @@ export interface IRelyingPartyLoginStart {
|
|
|
62
90
|
intentCookie: string;
|
|
63
91
|
}
|
|
64
92
|
|
|
65
|
-
/**
|
|
66
|
-
* How a bearer client receives the outcome of its login:
|
|
67
|
-
*
|
|
68
|
-
* - `web`: a redirect to a same-origin path of the application
|
|
69
|
-
* - `loopback`: a redirect to `http://127.0.0.1:<port>/…` or `http://[::1]:<port>/…`, any port (RFC 8252)
|
|
70
|
-
* - `custom_scheme`: a redirect to a registered private-use URI scheme such as `com.example.app:/signed-in` (RFC 8252)
|
|
71
|
-
* - `popup`: a page that posts the outcome to `window.opener` at exactly one registered origin
|
|
72
|
-
*/
|
|
73
|
-
export type TRelyingPartyBearerClientKind = 'web' | 'loopback' | 'custom_scheme' | 'popup';
|
|
74
|
-
|
|
75
93
|
/**
|
|
76
94
|
* The exact return targets a bearer client may name, per client kind. A kind
|
|
77
95
|
* without targets is not offered. Matching is by equality, never by prefix
|
|
@@ -154,27 +172,17 @@ export interface IRelyingPartyBearerTokens {
|
|
|
154
172
|
refreshTokenExpiresAt: number;
|
|
155
173
|
}
|
|
156
174
|
|
|
157
|
-
/**
|
|
158
|
-
* What the popup page of a `popup` delivery posts to `window.opener`. The
|
|
159
|
-
* receiver accepts it only when `event.origin` is the relying party's public
|
|
160
|
-
* origin, `event.source` is the popup it opened, `type` is
|
|
161
|
-
* `RELYING_PARTY_BEARER_MESSAGE_TYPE` and `state` is its own.
|
|
162
|
-
*/
|
|
163
|
-
export interface IRelyingPartyBearerMessage {
|
|
164
|
-
type: string;
|
|
165
|
-
/** the hand-off code; absent when the login failed */
|
|
166
|
-
code?: string;
|
|
167
|
-
/** why the login failed; absent when it succeeded */
|
|
168
|
-
error?: string;
|
|
169
|
-
state?: string;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
175
|
/** A provider access token usable right now, with the subject it belongs to. */
|
|
173
176
|
export interface IProviderToken {
|
|
174
177
|
accessToken: string;
|
|
175
178
|
subject: string;
|
|
176
179
|
/** epoch milliseconds; `0` when the provider named no lifetime */
|
|
177
180
|
expiresAt: number;
|
|
181
|
+
/**
|
|
182
|
+
* `true` only on the answer to `rejectedAccessToken`, and only when a
|
|
183
|
+
* rotation that this call started issued the token.
|
|
184
|
+
*/
|
|
185
|
+
rotatedAfterRejection?: boolean;
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
/** What an authority observer needs from the session owner. `RelyingPartySessions` implements it. */
|
|
@@ -182,8 +190,21 @@ export interface IAuthorityTokenSource {
|
|
|
182
190
|
/**
|
|
183
191
|
* The access token of a login session, rotated first when it is due. The
|
|
184
192
|
* session must belong to `expectedPrincipalIdArg`. Throws `AuthorityTokenError`.
|
|
193
|
+
*
|
|
194
|
+
* `rejectedAccessToken` is a token idp.global answered inactive for. The
|
|
195
|
+
* source must rotate the session's token then, whatever is stored, and set
|
|
196
|
+
* `rotatedAfterRejection` only when a rotation that this call started issued
|
|
197
|
+
* the token. A refused refresh ends the session and throws
|
|
198
|
+
* `authority_login_required`. A rotation that started before the call, one
|
|
199
|
+
* another replica holds, or a session without a refresh token marks
|
|
200
|
+
* nothing. A source that never rotates or never marks its answer can never
|
|
201
|
+
* refuse: the observer answers `authority_unavailable` (fail-closed).
|
|
185
202
|
*/
|
|
186
|
-
liveProviderToken(
|
|
203
|
+
liveProviderToken(
|
|
204
|
+
sessionIdArg: string,
|
|
205
|
+
expectedPrincipalIdArg: string,
|
|
206
|
+
optionsArg?: { rejectedAccessToken?: string },
|
|
207
|
+
): Promise<IProviderToken>;
|
|
187
208
|
}
|
|
188
209
|
|
|
189
210
|
/**
|
|
@@ -250,6 +271,8 @@ export type TRelyingPartyEventName =
|
|
|
250
271
|
| 'login_attempt_sweep_failed'
|
|
251
272
|
/** expired bearer hand-offs and token generations could not be swept */
|
|
252
273
|
| 'bearer_sweep_failed'
|
|
274
|
+
/** sessions an hour past their end could not be swept */
|
|
275
|
+
| 'session_sweep_failed'
|
|
253
276
|
/** a bearer hand-off code or refresh token came back after its use; `reason` names which, the session ended */
|
|
254
277
|
| 'bearer_credential_reused'
|
|
255
278
|
/** an application hook threw; the step it belonged to ran on regardless */
|