@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
package/readme.md
CHANGED
|
@@ -13,6 +13,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
13
13
|
| Runtime | Import | What it does |
|
|
14
14
|
| --- | --- | --- |
|
|
15
15
|
| Browser | `@idp.global/sdk/browser` | SSO client, JWT and refresh-token storage, login redirects, logout, transfer-token exchange, and typed request shortcuts for user, organization, billing, OIDC, and admin flows. |
|
|
16
|
+
| Browser, relying-party sign-in only | `@idp.global/sdk/browser/bearer` | `RelyingPartyBearerClient` and the bearer contract types, without the rest of the browser client; for applications whose server runs `@idp.global/sdk/relyingparty` in bearer mode. |
|
|
16
17
|
| Server | `@idp.global/sdk/server` | NoSQL-backed account store, scrypt password hashing, local/idp.global account authentication, a server-side typed-socket client for idp.global password login, and confidential OIDC introspection. |
|
|
17
18
|
| Relying party | `@idp.global/sdk/relyingparty` | The OIDC relying-party session layer: authorization code flow with PKCE on `openid-client`, login attempts and HttpOnly cookie sessions with CSRF tokens on NoSQL, sealed provider tokens rotated under a cross-replica lock, live organization authority, HTTP login routes, a TypedRouter actor scope and TypedSocket session binding. |
|
|
18
19
|
|
|
@@ -151,6 +152,7 @@ await idp.logout();
|
|
|
151
152
|
| `getTransferTokenAndSwitchToLocation()` | Request a transfer token and navigate to the same configured target with `transfertoken` attached. |
|
|
152
153
|
| `processTransferToken()` | Consume a `transfertoken` from the current URL and refresh local auth state. |
|
|
153
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). |
|
|
154
156
|
|
|
155
157
|
### Typed Request Shortcuts
|
|
156
158
|
|
|
@@ -381,6 +383,43 @@ in that application only once the reused answer expires — up to the configured
|
|
|
381
383
|
TTL — while ending the login session in the application drops its reused answers
|
|
382
384
|
at once. A failure to observe is never reused.
|
|
383
385
|
|
|
386
|
+
idp.global answers an introspection inactive for a login that ended as well as
|
|
387
|
+
for a membership that is gone. Only a refresh tells them apart: idp.global
|
|
388
|
+
refuses the refresh of an ended login (`invalid_grant`), and also when the user
|
|
389
|
+
can no longer sign in or an organization of the login is no longer active, but
|
|
390
|
+
rotates the token of a member who was removed from one organization. So an
|
|
391
|
+
inactive answer is not yet a refusal. `AuthorityObserver` first rotates the
|
|
392
|
+
session's provider token (`liveProviderToken(sessionId, principalId,
|
|
393
|
+
{ rejectedAccessToken })`) and introspects again with the new token:
|
|
394
|
+
|
|
395
|
+
- A refused rotation is the ended login. It ends the session
|
|
396
|
+
(`refresh_token_refused`, through `onSessionEnded`) and throws
|
|
397
|
+
`AuthorityTokenError` `authority_login_required`. No membership is refused.
|
|
398
|
+
- Only an inactive answer on a token from a rotation that started after the
|
|
399
|
+
observation's own inactive answer throws `AuthorityRefusedError`
|
|
400
|
+
`idp_membership_absent`. The source marks such a token
|
|
401
|
+
`rotatedAfterRejection`, and the observer orders inactive answers and
|
|
402
|
+
rotation starts by one sequence; it compares no tokens.
|
|
403
|
+
- Every other inactive answer throws `authority_unavailable` and keeps nothing:
|
|
404
|
+
when the rotation started before the answer, when another replica holds the
|
|
405
|
+
session's rotation, when a rotation was already running, and when the session
|
|
406
|
+
has no refresh token.
|
|
407
|
+
- An observer rotates a session's token for an inactive answer at most once per
|
|
408
|
+
`AUTHORITY_FORCED_REFRESH_INTERVAL_MS` (60 s). An inactive answer within that
|
|
409
|
+
time of the rotation is `authority_unavailable`, as is every one while a
|
|
410
|
+
rotation that failed stands. After it the next inactive answer rotates and
|
|
411
|
+
decides.
|
|
412
|
+
- A deactivated application is not a refusal either. idp.global answers its
|
|
413
|
+
refresh with `invalid_client`, which reads as `authority_unavailable`.
|
|
414
|
+
- idp.global counts refused token requests per client: after 10 in 10 minutes
|
|
415
|
+
without a successful one in between, it blocks the client's token requests
|
|
416
|
+
for 15 minutes. A rotation only refuses earlier what the session's next
|
|
417
|
+
refresh would refuse, once per session.
|
|
418
|
+
- A token source of an application's own (`IAuthorityTokenSource`) must rotate
|
|
419
|
+
when it is given `rejectedAccessToken` and mark the token. One that never
|
|
420
|
+
rotates or marks, as a 19.3.0 source does, can never refuse: its inactive
|
|
421
|
+
answers are `authority_unavailable`.
|
|
422
|
+
|
|
384
423
|
### Server-held OIDC UserInfo
|
|
385
424
|
|
|
386
425
|
Use `OidcUserInfoClient` to obtain current profile and organization picker data
|
|
@@ -562,7 +601,9 @@ What it does:
|
|
|
562
601
|
idpOrganizationId })` introspects the organization with the session's live
|
|
563
602
|
token and projects the role keys through the application's table. It answers
|
|
564
603
|
a grant or throws `AuthorityRefusedError` (with `firstRefusal` once per
|
|
565
|
-
transition, for an audit trail) or `AuthorityTokenError`.
|
|
604
|
+
transition, for an audit trail) or `AuthorityTokenError`. An inactive answer
|
|
605
|
+
rotates the token before it refuses, and a login that ended at idp.global ends
|
|
606
|
+
the session (`authority_login_required`); see the authority contract above.
|
|
566
607
|
- **Transports**: `createActorScope()` gives a TypedRouter request scope
|
|
567
608
|
(`addRequestScope`) that runs every dispatch as the actor its trusted local
|
|
568
609
|
data names. `RelyingPartySocketBinding` registers a TypedSocket connection
|
|
@@ -583,8 +624,9 @@ an https `publicOrigin`; on http the constructor refuses them, because browsers
|
|
|
583
624
|
would silently drop those cookies.
|
|
584
625
|
|
|
585
626
|
**Adopting existing session data.** The persisted fields and index declarations
|
|
586
|
-
are those nevermind.finance has stored since mainapp 0b693cb
|
|
587
|
-
|
|
627
|
+
are those nevermind.finance has stored since mainapp 0b693cb, and the session
|
|
628
|
+
model adds an `expiresAt` index, built on first use. An application that
|
|
629
|
+
already holds such collections binds the models to them and passes its own
|
|
588
630
|
cookie names and CSRF label, so live sessions and sealed tokens keep working:
|
|
589
631
|
|
|
590
632
|
```ts
|
|
@@ -604,6 +646,12 @@ A deployment without a provider (`provider` omitted) keeps the same session
|
|
|
604
646
|
store for sessions it opens itself with `openSession({ principalId })`, for
|
|
605
647
|
example after a local password check.
|
|
606
648
|
|
|
649
|
+
Nothing runs on a timer. A login start first removes up to 100 login attempts
|
|
650
|
+
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; `openSession` removes
|
|
652
|
+
the sessions only. Removing an ended session fires no end hook and calls no
|
|
653
|
+
provider.
|
|
654
|
+
|
|
607
655
|
**Building before the database connects.** `db` may be a function that answers
|
|
608
656
|
the database once it exists. It is called on every use of a model, the way
|
|
609
657
|
`@Collection(() => db)` resolves it, and never while the models,
|
|
@@ -802,7 +850,8 @@ Tokens and their ends:
|
|
|
802
850
|
provider answers 503 `authority_unavailable` and spends nothing. Until then
|
|
803
851
|
an access token works for its remaining lifetime. An application that must
|
|
804
852
|
see an idp.global logout sooner observes authority per request
|
|
805
|
-
(`authority.observe({ loginSessionId: live.session.id, … })`)
|
|
853
|
+
(`authority.observe({ loginSessionId: live.session.id, … })`), which ends the
|
|
854
|
+
session as soon as idp.global has ended the login.
|
|
806
855
|
|
|
807
856
|
The four API routes answer CORS, including the preflight, for the popup
|
|
808
857
|
origins exactly and never allow credentials; every other origin gets no CORS
|
|
@@ -815,23 +864,169 @@ The client side:
|
|
|
815
864
|
`Referrer-Policy: no-referrer`.
|
|
816
865
|
- A popup client opens the login with `window.open` (without `noopener`) and
|
|
817
866
|
accepts only the message of the popup it opened, from the relying party's
|
|
818
|
-
public origin, with its own `state
|
|
867
|
+
public origin, with its own `state`. The opener page must keep its opener
|
|
868
|
+
link to the popup: `Cross-Origin-Opener-Policy: same-origin` severs it, while
|
|
869
|
+
`same-origin-allow-popups` keeps it.
|
|
870
|
+
- Every client claims a code once and never retries a claim or a refresh. Its
|
|
871
|
+
refreshes run one at a time across everything that shares the pair, and a
|
|
872
|
+
rotated pair is stored, all of it at once, before its access token is first
|
|
873
|
+
used: a pair that is lost after the relying party rotated it cannot be
|
|
874
|
+
recovered, and its spent refresh token coming back ends the session. Native
|
|
875
|
+
clients implement this side themselves.
|
|
876
|
+
|
|
877
|
+
The HTTP answers are exported as types (`IRelyingPartyBearerTokenAnswer`,
|
|
878
|
+
`IRelyingPartyBearerSessionAnswer`, `IRelyingPartyBearerLogoutAnswer`,
|
|
879
|
+
`IRelyingPartyBearerErrorAnswer` with `TRelyingPartyBearerErrorCode`, and the
|
|
880
|
+
popup's `IRelyingPartyBearerMessage`). They come from a module that imports
|
|
881
|
+
nothing, so `@idp.global/sdk/browser` shares them with the server.
|
|
882
|
+
|
|
883
|
+
**The browser client.** `RelyingPartyBearerClient` from `@idp.global/sdk/browser`
|
|
884
|
+
is that client side for `web` and `popup` logins. The application owns where the
|
|
885
|
+
token pair lives: it passes a storage adapter, typically over IndexedDB, and the
|
|
886
|
+
client keeps nothing of its own across reloads.
|
|
887
|
+
|
|
888
|
+
```ts
|
|
889
|
+
import { RelyingPartyBearerClient, type IRelyingPartyBearerTokenStorage } from '@idp.global/sdk/browser';
|
|
890
|
+
|
|
891
|
+
const storage: IRelyingPartyBearerTokenStorage = {
|
|
892
|
+
get: () => tokenStore.get('bearer'), // the stored pair, or null
|
|
893
|
+
set: (tokens, { signal }) => tokenStore.put('bearer', tokens, { signal }), // resolves once durable, writes nothing once aborted
|
|
894
|
+
clear: ({ signal }) => tokenStore.delete('bearer', { signal }), // resolves once durable, removes nothing once aborted
|
|
895
|
+
};
|
|
896
|
+
const client = new RelyingPartyBearerClient({ origin: 'https://app.example', storage });
|
|
897
|
+
|
|
898
|
+
// web: start, and receive on the return target page
|
|
899
|
+
const login = await client.createLogin({ clientKind: 'web', returnTarget: '/signed-in' });
|
|
900
|
+
sessionStorage.setItem('bearer-login', JSON.stringify(login));
|
|
901
|
+
location.assign(login.url);
|
|
902
|
+
// … on /signed-in
|
|
903
|
+
const result = await client.completeRedirectLogin(JSON.parse(sessionStorage.getItem('bearer-login') ?? 'null'));
|
|
904
|
+
// result.tokens is the stored pair; result.previousSession says what became of the one it replaced
|
|
905
|
+
|
|
906
|
+
// popup: make the login first, open it in the click handler itself
|
|
907
|
+
const popupLogin = await client.createLogin({ clientKind: 'popup', returnTarget: 'https://workspace.example' });
|
|
908
|
+
button.onclick = () => client.openPopupLogin(popupLogin).then(showWorkspace, showSignInError);
|
|
909
|
+
|
|
910
|
+
// every call
|
|
911
|
+
const accessToken = await client.accessToken(); // null without a session
|
|
912
|
+
// after the relying party refused one
|
|
913
|
+
await client.refresh(rejectedAccessToken);
|
|
914
|
+
await client.logout();
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
- `createLogin` makes the verifier, its S256 challenge and the state from 32
|
|
918
|
+
random bytes each and answers them with the start URL. The pending login is
|
|
919
|
+
the application's to keep until it comes back; it holds the verifier.
|
|
920
|
+
- `completeRedirectLogin(login)` answers `null` on a page without a delivery.
|
|
921
|
+
It removes `code`, `error` and `state` from the address first, then refuses a
|
|
922
|
+
state that is not the login's (`state_mismatch`, nothing claimed) and claims
|
|
923
|
+
the code once (`already_received` after that).
|
|
924
|
+
- A received login (`IRelyingPartyBearerLoginResult`) replaces the stored pair.
|
|
925
|
+
After the claim, under the lock, the client stores the new pair and then
|
|
926
|
+
ends the session of the pair it replaced through `bearer/logout`, with that
|
|
927
|
+
pair held in memory. `previousSession` is `no_previous_session`,
|
|
928
|
+
`same_session` (nothing to end), `previous_session_ended`, or
|
|
929
|
+
`previous_session_not_revoked` with `previousSessionError`. In the last case
|
|
930
|
+
that session lives on at the relying party until it expires. A failed claim
|
|
931
|
+
leaves the stored pair and its session as they were.
|
|
932
|
+
- `openPopupLogin(login, { timeoutMs?, signal?, windowFeatures? })` calls
|
|
933
|
+
`window.open` before it returns, so a browser lets it through within the
|
|
934
|
+
click. It takes only a message from the relying party's exact origin, from
|
|
935
|
+
this popup, of `RELYING_PARTY_BEARER_MESSAGE_TYPE` and with the login's
|
|
936
|
+
state; it ignores every other. It ends with `popup_blocked`, `popup_closed`,
|
|
937
|
+
`popup_timeout` (10 minutes by default) or the signal's reason.
|
|
938
|
+
- `accessToken()` answers the stored access token while it is fresh, and
|
|
939
|
+
otherwise rotates it first. `refreshSkewMs` (one minute by default, at most
|
|
940
|
+
half the lifetime) is the margin. `refresh(rejectedAccessToken?)` rotates now.
|
|
941
|
+
Both answer `null`, and clear the storage, once the session is over
|
|
942
|
+
(`invalid_bearer_grant`).
|
|
943
|
+
- Every refresh, logout and store of a claimed pair holds one Web Lock
|
|
944
|
+
(`lockName`; by default per origin), so the tabs of an origin rotate one at a
|
|
945
|
+
time. Inside the lock the pair is read from the storage again. A pair another
|
|
946
|
+
tab rotated is used as it is, so concurrent refreshes make one request. A
|
|
947
|
+
rotated pair is stored before the lock is released and before its access
|
|
948
|
+
token is answered; when `storage.set` fails, the new pair is never used and
|
|
949
|
+
the session is lost.
|
|
950
|
+
- `requestTimeoutMs` (30 seconds by default, more than 0 and at most two
|
|
951
|
+
minutes; any other value throws) is the deadline of each operation: the
|
|
952
|
+
claim of a login's code, a refresh with the write of its pair, the store of a
|
|
953
|
+
claimed pair with the revoke of the one it replaced, and a logout. When it
|
|
954
|
+
fires, the request is aborted, the signal a storage write got aborts, an
|
|
955
|
+
answer that still arrives is dropped unstored, the lock is released for the other
|
|
956
|
+
tabs and the call fails with `request_failed`; a login whose pair is stored
|
|
957
|
+
by then stands, with `previous_session_not_revoked`. A claim or refresh the
|
|
958
|
+
relying party carried out before the deadline is lost with its answer. A
|
|
959
|
+
timed-out claim fails the login, and the user signs in again; after a lost
|
|
960
|
+
refresh, the next refresh ends the session (`refresh_token_reused`) and the
|
|
961
|
+
user signs in again. Strict rotation costs that.
|
|
962
|
+
- `storage.set(tokens, { signal })` and `storage.clear({ signal })` either
|
|
963
|
+
honour the signal, so a write it aborted is not made and the call rejects,
|
|
964
|
+
or complete synchronously, as an adapter over `localStorage` does. The lock
|
|
965
|
+
is never held past the deadline, so an adapter that ignores the signal can
|
|
966
|
+
finish a write after another tab took the lock. A late `set` overwrites what
|
|
967
|
+
that tab stored or cleared, and the session can then end
|
|
968
|
+
(`refresh_token_reused`); a late `clear` removes the pair that tab stored.
|
|
969
|
+
Either way the user signs in again.
|
|
970
|
+
- `logout()` clears the storage and then ends the session at the relying party
|
|
971
|
+
with the pair it held. When that request fails, is refused or reaches the
|
|
972
|
+
deadline, it throws `session_not_revoked` with the request's error as its
|
|
973
|
+
`cause`: the device is signed out, the session may live on at the relying
|
|
974
|
+
party until it expires, and the application can tell the user they are
|
|
975
|
+
signed out on this device only. A deadline that fires before the storage is
|
|
976
|
+
cleared throws `request_failed` and leaves the pair.
|
|
977
|
+
- Failures are `RelyingPartyBearerClientError`s with a `code`. A refusal of the
|
|
978
|
+
relying party is `request_refused` with its `status` and `answerError`; an
|
|
979
|
+
unreachable one is `request_failed`, which leaves the stored pair as it was,
|
|
980
|
+
except in `logout()` (`session_not_revoked`).
|
|
981
|
+
- The client needs the Web Locks API and Web Crypto, which a browser offers
|
|
982
|
+
only in a secure context, and has no fallback for either. Every request is
|
|
983
|
+
sent without credentials, cache or redirects.
|
|
984
|
+
|
|
985
|
+
**Bearer-only mode.** An application whose every client is a bearer client turns
|
|
986
|
+
the cookie login off with `cookieSessions: false`. No route then reads or sets a
|
|
987
|
+
cookie, and that includes the intent cookie a cookie-mode callback clears. It
|
|
988
|
+
takes no `csrfKey`, and it refuses `csrfKey`, `csrfLabel` and `cookieNames`,
|
|
989
|
+
because nothing would use them; `bearer` and `provider` are required.
|
|
819
990
|
|
|
820
991
|
```ts
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
headers: { 'content-type': 'application/json' },
|
|
830
|
-
body: JSON.stringify({ code: message.code, codeVerifier: verifier }),
|
|
831
|
-
}).then((response) => response.json());
|
|
992
|
+
const sessions = new RelyingPartySessions({
|
|
993
|
+
cookieSessions: false,
|
|
994
|
+
models,
|
|
995
|
+
provider,
|
|
996
|
+
publicOrigin: 'https://app.example',
|
|
997
|
+
sessionEncryptionKey: decodeSecretKey(process.env.SESSION_ENCRYPTION_KEY!, 'SESSION_ENCRYPTION_KEY'),
|
|
998
|
+
principals,
|
|
999
|
+
bearer: { returnTargets: { web: ['/signed-in'], popup: ['https://workspace.example'] } },
|
|
832
1000
|
});
|
|
833
1001
|
```
|
|
834
1002
|
|
|
1003
|
+
`/auth/login`, `/auth/finalize`, `/auth/session` and `/auth/logout` answer
|
|
1004
|
+
`null`, like every path the routes do not serve. `startLogin`,
|
|
1005
|
+
`completeLogin`, `abandonLogin`, `finalizeLogin`, `openSession`,
|
|
1006
|
+
`clearSessionCookie` and `clearIntentCookie` throw `login_method_unavailable`,
|
|
1007
|
+
and `resolveCookieSession` answers `null`. A callback that belongs to no bearer
|
|
1008
|
+
login redirects to `failureLocation` when the provider sent an `error`, and
|
|
1009
|
+
otherwise answers 400 `authentication_failed`. `RelyingPartySocketBinding`
|
|
1010
|
+
registers a browser by its cookie session, so it binds no connection in this
|
|
1011
|
+
mode; an application with sockets authenticates them itself, for example with
|
|
1012
|
+
`resolveBearerSession`.
|
|
1013
|
+
|
|
1014
|
+
**Listing a principal's sessions.** `sessionsOfPrincipal(principalId, { limit?,
|
|
1015
|
+
cursor? })` answers one page of the principal's live sessions, newest first,
|
|
1016
|
+
cookie and bearer alike: `{ sessions, nextCursor? }`. `limit` is 1 to 1,000
|
|
1017
|
+
(100 by default). `nextCursor` is there while more may follow. A session live
|
|
1018
|
+
throughout the walk appears on exactly one page; one opened during the walk is
|
|
1019
|
+
newer than every cursor and appears in the next walk. The cursor is opaque; a malformed one
|
|
1020
|
+
throws `invalid_sessions_cursor` (400). An inactive principal has no live
|
|
1021
|
+
session.
|
|
1022
|
+
|
|
1023
|
+
```ts
|
|
1024
|
+
let page = await sessions.sessionsOfPrincipal(principalId, { limit: 50 });
|
|
1025
|
+
while (page.nextCursor) {
|
|
1026
|
+
page = await sessions.sessionsOfPrincipal(principalId, { limit: 50, cursor: page.nextCursor });
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
835
1030
|
### Testing an Application
|
|
836
1031
|
|
|
837
1032
|
`@idp.global/sdk/relyingparty/testing` exports `RelyingPartyProviderDouble`, an
|
|
@@ -893,13 +1088,14 @@ const delivery = await readBearerDelivery(await fetch(callback, { redirect: 'man
|
|
|
893
1088
|
| --- | --- |
|
|
894
1089
|
| `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. |
|
|
895
1090
|
| `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. |
|
|
896
|
-
| `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`, `liveProviderToken
|
|
897
|
-
| `AuthorityObserver` | Live organization authority with opt-in positive and negative reuse. |
|
|
898
|
-
| `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
|
|
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. |
|
|
1092
|
+
| `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
|
+
| `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`. |
|
|
899
1094
|
| `createActorScope()` | AsyncLocalStorage actor context with a TypedRouter request scope. |
|
|
900
1095
|
| `RelyingPartySocketBinding` | TypedSocket browser registration, per-request re-validation and session tags; takes a connection wrapper or its peer. |
|
|
901
1096
|
| `SecretBox`, `deriveCsrfToken()`, `decodeSecretKey()`, `parseCookies()`, `serializeCookie()`, `sanitizeReturnPath()` | The primitives underneath. |
|
|
902
1097
|
| `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
|
+
| `IRelyingPartyBearerTokenAnswer`, `IRelyingPartyBearerSessionAnswer`, `IRelyingPartyBearerLogoutAnswer`, `IRelyingPartyBearerErrorAnswer`, `TRelyingPartyBearerErrorCode`, `IRelyingPartyBearerMessage` | The HTTP answers of the bearer routes and the popup message, shared with `@idp.global/sdk/browser`. |
|
|
903
1099
|
| `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. |
|
|
904
1100
|
| `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. |
|
|
905
1101
|
|