@idp.global/sdk 18.0.1 → 18.2.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.
Files changed (50) hide show
  1. package/changelog.md +28 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts_relyingparty/classes.actorscope.d.ts +23 -0
  4. package/dist_ts_relyingparty/classes.actorscope.js +22 -0
  5. package/dist_ts_relyingparty/classes.authorityobserver.d.ts +81 -0
  6. package/dist_ts_relyingparty/classes.authorityobserver.js +185 -0
  7. package/dist_ts_relyingparty/classes.errors.d.ts +37 -0
  8. package/dist_ts_relyingparty/classes.errors.js +60 -0
  9. package/dist_ts_relyingparty/classes.idpoidcprovider.d.ts +142 -0
  10. package/dist_ts_relyingparty/classes.idpoidcprovider.js +255 -0
  11. package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +102 -0
  12. package/dist_ts_relyingparty/classes.relyingpartyroutes.js +248 -0
  13. package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +223 -0
  14. package/dist_ts_relyingparty/classes.relyingpartysessions.js +825 -0
  15. package/dist_ts_relyingparty/classes.rpmodels.d.ts +87 -0
  16. package/dist_ts_relyingparty/classes.rpmodels.js +302 -0
  17. package/dist_ts_relyingparty/classes.secretbox.d.ts +14 -0
  18. package/dist_ts_relyingparty/classes.secretbox.js +45 -0
  19. package/dist_ts_relyingparty/classes.socketbinding.d.ts +84 -0
  20. package/dist_ts_relyingparty/classes.socketbinding.js +100 -0
  21. package/dist_ts_relyingparty/helpers.cookies.d.ts +11 -0
  22. package/dist_ts_relyingparty/helpers.cookies.js +62 -0
  23. package/dist_ts_relyingparty/helpers.crypto.d.ts +12 -0
  24. package/dist_ts_relyingparty/helpers.crypto.js +40 -0
  25. package/dist_ts_relyingparty/helpers.returnpath.d.ts +9 -0
  26. package/dist_ts_relyingparty/helpers.returnpath.js +39 -0
  27. package/dist_ts_relyingparty/index.d.ts +13 -0
  28. package/dist_ts_relyingparty/index.js +14 -0
  29. package/dist_ts_relyingparty/interfaces.d.ts +142 -0
  30. package/dist_ts_relyingparty/interfaces.js +2 -0
  31. package/dist_ts_relyingparty/plugins.d.ts +10 -0
  32. package/dist_ts_relyingparty/plugins.js +11 -0
  33. package/package.json +13 -10
  34. package/readme.md +215 -4
  35. package/ts/00_commitinfo_data.ts +1 -1
  36. package/ts_relyingparty/classes.actorscope.ts +39 -0
  37. package/ts_relyingparty/classes.authorityobserver.ts +253 -0
  38. package/ts_relyingparty/classes.errors.ts +77 -0
  39. package/ts_relyingparty/classes.idpoidcprovider.ts +386 -0
  40. package/ts_relyingparty/classes.relyingpartyroutes.ts +319 -0
  41. package/ts_relyingparty/classes.relyingpartysessions.ts +1017 -0
  42. package/ts_relyingparty/classes.rpmodels.ts +170 -0
  43. package/ts_relyingparty/classes.secretbox.ts +49 -0
  44. package/ts_relyingparty/classes.socketbinding.ts +156 -0
  45. package/ts_relyingparty/helpers.cookies.ts +79 -0
  46. package/ts_relyingparty/helpers.crypto.ts +49 -0
  47. package/ts_relyingparty/helpers.returnpath.ts +44 -0
  48. package/ts_relyingparty/index.ts +13 -0
  49. package/ts_relyingparty/interfaces.ts +165 -0
  50. package/ts_relyingparty/plugins.ts +17 -0
@@ -0,0 +1,100 @@
1
+ import * as plugins from './plugins.js';
2
+ import { sha256, timingSafeEqualString } from './helpers.crypto.js';
3
+ const peerOf = (targetArg) => 'peer' in targetArg ? targetArg.peer : targetArg;
4
+ /**
5
+ * Binds typed socket connections to relying-party browser sessions. A
6
+ * connection registers once with the CSRF token of the page; the proof is the
7
+ * upgrade's own `Origin` and `Cookie` — which only a browser on this origin
8
+ * sends — plus the token, and the connection is pinned to the session. Every
9
+ * later request re-validates the session with the same strength as an HTTP
10
+ * request. Connection limits, rate limits and sweeps belong to the
11
+ * application's socket gate.
12
+ */
13
+ export class RelyingPartySocketBinding {
14
+ #sessions;
15
+ #transport;
16
+ tagPrefix;
17
+ constructor(optionsArg) {
18
+ this.#sessions = optionsArg.sessions;
19
+ this.#transport = optionsArg.transport;
20
+ this.tagPrefix = optionsArg.tagPrefix ?? 'idp_rp_session:';
21
+ }
22
+ /** The upgrade headers a connection (or its peer) arrived with. */
23
+ upgradeOf(targetArg) {
24
+ const headers = peerOf(targetArg).context.headers;
25
+ return { origin: headers.get('origin'), cookie: headers.get('cookie') };
26
+ }
27
+ /**
28
+ * What a registration payload `{ csrfToken }` proves, given the upgrade it
29
+ * came on. No connection state is touched, so the application decides about
30
+ * its limits between this answer and `tagConnection`.
31
+ */
32
+ async resolveBrowserRegistration(payloadArg, upgradeArg) {
33
+ if (!payloadArg || typeof payloadArg !== 'object' || !Reflect.has(payloadArg, 'csrfToken')) {
34
+ return { registered: false, refusal: { code: 'invalid_input', status: 400 } };
35
+ }
36
+ if (upgradeArg.origin !== this.#sessions.publicOrigin) {
37
+ return { registered: false, refusal: { code: 'forbidden', status: 403 } };
38
+ }
39
+ const resolved = await this.#sessions.resolveCookieSession(upgradeArg.cookie);
40
+ if (!resolved) {
41
+ return { registered: false, refusal: { code: 'unauthorized', status: 401 } };
42
+ }
43
+ if (!this.#sessions.validateCsrf(resolved, Reflect.get(payloadArg, 'csrfToken'))) {
44
+ return { registered: false, refusal: { code: 'forbidden', status: 403 } };
45
+ }
46
+ return {
47
+ registered: true,
48
+ pinned: {
49
+ sessionId: resolved.session.id,
50
+ principalId: resolved.session.principalId,
51
+ csrfTokenHash: sha256(resolved.csrfToken),
52
+ },
53
+ expiresAt: resolved.session.expiresAt,
54
+ };
55
+ }
56
+ tagOf(sessionIdArg) {
57
+ return this.tagPrefix + sessionIdArg;
58
+ }
59
+ /**
60
+ * Tags a connection — its wrapper or its peer — with its session; throws
61
+ * when the transport is not running or the connection is gone.
62
+ */
63
+ tagConnection(targetArg, sessionIdArg) {
64
+ const transport = this.#transport();
65
+ if (!transport) {
66
+ throw new Error('The socket transport is not running');
67
+ }
68
+ transport.setServerTag(targetArg, this.tagOf(sessionIdArg));
69
+ }
70
+ /**
71
+ * The pinned session as it stands right now: re-read, live, its principal
72
+ * active and unchanged (and resolved, with `principals.resolve`), touched
73
+ * under the liveness it was read with. The
74
+ * browser keeps proving itself with its CSRF token, compared against the
75
+ * hash the registration kept. Nothing of this is cached.
76
+ */
77
+ async revalidate(pinnedArg) {
78
+ const live = await this.#sessions.revalidateLiveSession(pinnedArg.sessionId);
79
+ if (!live || live.session.principalId !== pinnedArg.principalId) {
80
+ return null;
81
+ }
82
+ const expectedHash = pinnedArg.csrfTokenHash;
83
+ return {
84
+ session: live.session,
85
+ principal: live.principal,
86
+ verifyCsrf: (candidateArg) => typeof candidateArg === 'string'
87
+ && expectedHash.length > 0
88
+ && timingSafeEqualString(expectedHash, sha256(candidateArg)),
89
+ };
90
+ }
91
+ /** The live connections of one session, for closing them when it ends. */
92
+ async connectionsOfSession(sessionIdArg) {
93
+ const transport = this.#transport();
94
+ if (!transport) {
95
+ return [];
96
+ }
97
+ return transport.findAllTargetConnectionsByTag(this.tagOf(sessionIdArg));
98
+ }
99
+ }
100
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zb2NrZXRiaW5kaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfcmVseWluZ3BhcnR5L2NsYXNzZXMuc29ja2V0YmluZGluZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUV4QyxPQUFPLEVBQUUsTUFBTSxFQUFFLHFCQUFxQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFpQnBFLE1BQU0sTUFBTSxHQUFHLENBQUMsU0FBb0MsRUFBMkIsRUFBRSxDQUMvRSxNQUFNLElBQUksU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUEyQm5EOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxPQUFPLHlCQUF5QjtJQUMzQixTQUFTLENBQW1DO0lBQzVDLFVBQVUsQ0FBaUQ7SUFDcEQsU0FBUyxDQUFTO0lBRWxDLFlBQVksVUFBeUQ7UUFDbkUsSUFBSSxDQUFDLFNBQVMsR0FBRyxVQUFVLENBQUMsUUFBUSxDQUFDO1FBQ3JDLElBQUksQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFDLFNBQVMsQ0FBQztRQUN2QyxJQUFJLENBQUMsU0FBUyxHQUFHLFVBQVUsQ0FBQyxTQUFTLElBQUksaUJBQWlCLENBQUM7SUFDN0QsQ0FBQztJQUVELG1FQUFtRTtJQUM1RCxTQUFTLENBQUMsU0FBb0M7UUFDbkQsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUM7UUFDbEQsT0FBTyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7SUFDMUUsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsMEJBQTBCLENBQ3JDLFVBQW1CLEVBQ25C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
@@ -0,0 +1,11 @@
1
+ export interface ICookieOptions {
2
+ httpOnly?: boolean;
3
+ maxAgeSeconds?: number;
4
+ sameSite?: 'Lax' | 'Strict' | 'None';
5
+ secure?: boolean;
6
+ }
7
+ /** The cookies of a `Cookie` header; the first value of a repeated name wins, an undecodable value is skipped. */
8
+ export declare const parseCookies: (cookieHeaderArg: string | null | undefined) => Map<string, string>;
9
+ /** One `Set-Cookie` value, always `Path=/` and never with a `Domain`, so a `__Host-` name stays valid. */
10
+ export declare const serializeCookie: (nameArg: string, valueArg: string, optionsArg?: ICookieOptions) => string;
11
+ export declare const serializeClearedCookie: (nameArg: string, optionsArg?: Omit<ICookieOptions, "maxAgeSeconds">) => string;
@@ -0,0 +1,62 @@
1
+ // Control characters, space, DEL and the cookie delimiters are never valid.
2
+ const isInvalidCookieCharacter = (characterArg) => {
3
+ const code = characterArg.charCodeAt(0);
4
+ return code <= 0x20 || code === 0x7f || characterArg === ';' || characterArg === ',';
5
+ };
6
+ const assertCookiePart = (valueArg, labelArg) => {
7
+ if (!valueArg || Array.from(valueArg).some(isInvalidCookieCharacter)) {
8
+ throw new Error('Invalid cookie ' + labelArg);
9
+ }
10
+ };
11
+ /** The cookies of a `Cookie` header; the first value of a repeated name wins, an undecodable value is skipped. */
12
+ export const parseCookies = (cookieHeaderArg) => {
13
+ const result = new Map();
14
+ if (!cookieHeaderArg) {
15
+ return result;
16
+ }
17
+ for (const part of cookieHeaderArg.split(';')) {
18
+ const separatorIndex = part.indexOf('=');
19
+ if (separatorIndex <= 0) {
20
+ continue;
21
+ }
22
+ const name = part.slice(0, separatorIndex).trim();
23
+ const encodedValue = part.slice(separatorIndex + 1).trim();
24
+ if (!name || result.has(name)) {
25
+ continue;
26
+ }
27
+ try {
28
+ result.set(name, decodeURIComponent(encodedValue));
29
+ }
30
+ catch {
31
+ continue;
32
+ }
33
+ }
34
+ return result;
35
+ };
36
+ /** One `Set-Cookie` value, always `Path=/` and never with a `Domain`, so a `__Host-` name stays valid. */
37
+ export const serializeCookie = (nameArg, valueArg, optionsArg = {}) => {
38
+ assertCookiePart(nameArg, 'name');
39
+ assertCookiePart(valueArg, 'value');
40
+ const parts = [nameArg + '=' + encodeURIComponent(valueArg), 'Path=/'];
41
+ if (optionsArg.httpOnly) {
42
+ parts.push('HttpOnly');
43
+ }
44
+ if (optionsArg.secure) {
45
+ parts.push('Secure');
46
+ }
47
+ if (optionsArg.sameSite) {
48
+ parts.push('SameSite=' + optionsArg.sameSite);
49
+ }
50
+ if (optionsArg.maxAgeSeconds !== undefined) {
51
+ if (!Number.isSafeInteger(optionsArg.maxAgeSeconds) || optionsArg.maxAgeSeconds < 0) {
52
+ throw new Error('Invalid cookie max age');
53
+ }
54
+ parts.push('Max-Age=' + optionsArg.maxAgeSeconds);
55
+ }
56
+ return parts.join('; ');
57
+ };
58
+ export const serializeClearedCookie = (nameArg, optionsArg = {}) => serializeCookie(nameArg, 'deleted', {
59
+ ...optionsArg,
60
+ maxAgeSeconds: 0,
61
+ }) + '; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
62
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5jb29raWVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfcmVseWluZ3BhcnR5L2hlbHBlcnMuY29va2llcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFPQSw0RUFBNEU7QUFDNUUsTUFBTSx3QkFBd0IsR0FBRyxDQUFDLFlBQW9CLEVBQVcsRUFBRTtJQUNqRSxNQUFNLElBQUksR0FBRyxZQUFZLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3hDLE9BQU8sSUFBSSxJQUFJLElBQUksSUFBSSxJQUFJLEtBQUssSUFBSSxJQUFJLFlBQVksS0FBSyxHQUFHLElBQUksWUFBWSxLQUFLLEdBQUcsQ0FBQztBQUN2RixDQUFDLENBQUM7QUFFRixNQUFNLGdCQUFnQixHQUFHLENBQUMsUUFBZ0IsRUFBRSxRQUFnQixFQUFRLEVBQUU7SUFDcEUsSUFBSSxDQUFDLFFBQVEsSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxFQUFFLENBQUM7UUFDckUsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsR0FBRyxRQUFRLENBQUMsQ0FBQztJQUNoRCxDQUFDO0FBQ0gsQ0FBQyxDQUFDO0FBRUYsa0hBQWtIO0FBQ2xILE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLGVBQTBDLEVBQXVCLEVBQUU7SUFDOUYsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFDekMsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1FBQ3JCLE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFDRCxLQUFLLE1BQU0sSUFBSSxJQUFJLGVBQWUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUM5QyxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3pDLElBQUksY0FBYyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ3hCLFNBQVM7UUFDWCxDQUFDO1FBQ0QsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsY0FBYyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDbEQsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0QsSUFBSSxDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDOUIsU0FBUztRQUNYLENBQUM7UUFDRCxJQUFJLENBQUM7WUFDSCxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDO1FBQ3JELENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxTQUFTO1FBQ1gsQ0FBQztJQUNILENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDLENBQUM7QUFFRiwwR0FBMEc7QUFDMUcsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLENBQzdCLE9BQWUsRUFDZixRQUFnQixFQUNoQixhQUE2QixFQUFFLEVBQ3ZCLEVBQUU7SUFDVixnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDbEMsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3BDLE1BQU0sS0FBSyxHQUFHLENBQUMsT0FBTyxHQUFHLEdBQUcsR0FBRyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUN2RSxJQUFJLFVBQVUsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN4QixLQUFLLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFDRCxJQUFJLFVBQVUsQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN0QixLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3ZCLENBQUM7SUFDRCxJQUFJLFVBQVUsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN4QixLQUFLLENBQUMsSUFBSSxDQUFDLFdBQVcsR0FBRyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUNELElBQUksVUFBVSxDQUFDLGFBQWEsS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUMzQyxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLElBQUksVUFBVSxDQUFDLGFBQWEsR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNwRixNQUFNLElBQUksS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUM7UUFDNUMsQ0FBQztRQUNELEtBQUssQ0FBQyxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUNwRCxDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQzFCLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLENBQ3BDLE9BQWUsRUFDZixhQUFvRCxFQUFFLEVBQzlDLEVBQUUsQ0FDVixlQUFlLENBQUMsT0FBTyxFQUFFLFNBQVMsRUFBRTtJQUNsQyxHQUFHLFVBQVU7SUFDYixhQUFhLEVBQUUsQ0FBQztDQUNqQixDQUFDLEdBQUcseUNBQXlDLENBQUMifQ==
@@ -0,0 +1,12 @@
1
+ /** A 32-byte key given as base64 or base64url; anything else is refused by `labelArg`, never echoed. */
2
+ export declare const decodeSecretKey: (valueArg: string, labelArg: string) => Buffer;
3
+ export declare const randomToken: (byteLengthArg?: number) => string;
4
+ /** base64url SHA-256 of a UTF-8 string; how tokens, states and bindings are stored. */
5
+ export declare const sha256: (valueArg: string) => string;
6
+ export declare const timingSafeEqualString: (leftArg: string, rightArg: string) => boolean;
7
+ /**
8
+ * The CSRF token of a session: an HMAC of the session cookie token under the
9
+ * deployment's CSRF key, after a label that separates this use of the key from
10
+ * any other. It is derived again on every request and never stored.
11
+ */
12
+ export declare const deriveCsrfToken: (sessionTokenArg: string, csrfKeyArg: Buffer, labelArg: string) => string;
@@ -0,0 +1,40 @@
1
+ import * as plugins from './plugins.js';
2
+ /** A 32-byte key given as base64 or base64url; anything else is refused by `labelArg`, never echoed. */
3
+ export const decodeSecretKey = (valueArg, labelArg) => {
4
+ const normalized = typeof valueArg === 'string' ? valueArg.trim() : '';
5
+ if (!normalized) {
6
+ throw new Error(labelArg + ' must not be empty');
7
+ }
8
+ const base64Normalized = normalized
9
+ .replace(/-/gu, '+')
10
+ .replace(/_/gu, '/')
11
+ .replace(/=+$/u, '');
12
+ const decoded = Buffer.from(normalized, 'base64');
13
+ if (decoded.length !== 32
14
+ || decoded.toString('base64').replace(/=+$/u, '') !== base64Normalized) {
15
+ throw new Error(labelArg + ' must decode to exactly 32 bytes');
16
+ }
17
+ return decoded;
18
+ };
19
+ export const randomToken = (byteLengthArg = 32) => plugins.crypto.randomBytes(byteLengthArg).toString('base64url');
20
+ /** base64url SHA-256 of a UTF-8 string; how tokens, states and bindings are stored. */
21
+ export const sha256 = (valueArg) => plugins.crypto.createHash('sha256').update(valueArg, 'utf8').digest('base64url');
22
+ export const timingSafeEqualString = (leftArg, rightArg) => {
23
+ const left = Buffer.from(leftArg, 'utf8');
24
+ const right = Buffer.from(rightArg, 'utf8');
25
+ if (left.length !== right.length) {
26
+ return false;
27
+ }
28
+ return plugins.crypto.timingSafeEqual(left, right);
29
+ };
30
+ /**
31
+ * The CSRF token of a session: an HMAC of the session cookie token under the
32
+ * deployment's CSRF key, after a label that separates this use of the key from
33
+ * any other. It is derived again on every request and never stored.
34
+ */
35
+ export const deriveCsrfToken = (sessionTokenArg, csrfKeyArg, labelArg) => plugins.crypto
36
+ .createHmac('sha256', csrfKeyArg)
37
+ .update(labelArg, 'utf8')
38
+ .update(sessionTokenArg, 'utf8')
39
+ .digest('base64url');
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5jcnlwdG8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19yZWx5aW5ncGFydHkvaGVscGVycy5jcnlwdG8udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFFeEMsd0dBQXdHO0FBQ3hHLE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxDQUFDLFFBQWdCLEVBQUUsUUFBZ0IsRUFBVSxFQUFFO0lBQzVFLE1BQU0sVUFBVSxHQUFHLE9BQU8sUUFBUSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDdkUsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2hCLE1BQU0sSUFBSSxLQUFLLENBQUMsUUFBUSxHQUFHLG9CQUFvQixDQUFDLENBQUM7SUFDbkQsQ0FBQztJQUNELE1BQU0sZ0JBQWdCLEdBQUcsVUFBVTtTQUNoQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQztTQUNuQixPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQztTQUNuQixPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZCLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ2xELElBQ0UsT0FBTyxDQUFDLE1BQU0sS0FBSyxFQUFFO1dBQ2xCLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsS0FBSyxnQkFBZ0IsRUFDdEUsQ0FBQztRQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsUUFBUSxHQUFHLGtDQUFrQyxDQUFDLENBQUM7SUFDakUsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBRyxDQUFDLGdCQUF3QixFQUFFLEVBQVUsRUFBRSxDQUNoRSxPQUFPLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxhQUFhLENBQUMsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7QUFFbEUsdUZBQXVGO0FBQ3ZGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxDQUFDLFFBQWdCLEVBQVUsRUFBRSxDQUNqRCxPQUFPLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUVuRixNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLE9BQWUsRUFBRSxRQUFnQixFQUFXLEVBQUU7SUFDbEYsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDMUMsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDNUMsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNqQyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFDRCxPQUFPLE9BQU8sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztBQUNyRCxDQUFDLENBQUM7QUFFRjs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLENBQUMsZUFBdUIsRUFBRSxVQUFrQixFQUFFLFFBQWdCLEVBQVUsRUFBRSxDQUN2RyxPQUFPLENBQUMsTUFBTTtLQUNYLFVBQVUsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDO0tBQ2hDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDO0tBQ3hCLE1BQU0sQ0FBQyxlQUFlLEVBQUUsTUFBTSxDQUFDO0tBQy9CLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyJ9
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The one path a login may return to, judged on the normalized result and
3
+ * never on the shape of the input: `/.//evil.com` parses on this origin but
4
+ * normalizes to `//evil.com`, which a browser reads as another host. Everything
5
+ * that is not a plain same-origin path outside the login routes — a scheme, a
6
+ * host, a backslash, a control character, an overlong value, a path under
7
+ * `loginPathPrefixArg` — becomes `/`.
8
+ */
9
+ export declare const sanitizeReturnPath: (returnPathArg: unknown, publicOriginArg: string, loginPathPrefixArg: string) => string;
@@ -0,0 +1,39 @@
1
+ /** A return path is a location the application produced, not a place to park data. */
2
+ const MAX_RETURN_PATH_LENGTH = 1_024;
3
+ /**
4
+ * The one path a login may return to, judged on the normalized result and
5
+ * never on the shape of the input: `/.//evil.com` parses on this origin but
6
+ * normalizes to `//evil.com`, which a browser reads as another host. Everything
7
+ * that is not a plain same-origin path outside the login routes — a scheme, a
8
+ * host, a backslash, a control character, an overlong value, a path under
9
+ * `loginPathPrefixArg` — becomes `/`.
10
+ */
11
+ export const sanitizeReturnPath = (returnPathArg, publicOriginArg, loginPathPrefixArg) => {
12
+ const candidate = typeof returnPathArg === 'string' ? returnPathArg.trim() : '';
13
+ if (!candidate.startsWith('/')
14
+ || candidate.length > MAX_RETURN_PATH_LENGTH
15
+ || candidate.includes('\\')
16
+ // the URL parser drops tabs and newlines silently; such a path is refused instead of mangled
17
+ || /\p{Cc}/u.test(candidate)) {
18
+ return '/';
19
+ }
20
+ const loginRoot = loginPathPrefixArg.endsWith('/') ? loginPathPrefixArg.slice(0, -1) : loginPathPrefixArg;
21
+ try {
22
+ const parsed = new URL(candidate, publicOriginArg);
23
+ if (parsed.origin !== publicOriginArg) {
24
+ return '/';
25
+ }
26
+ if (parsed.pathname === loginRoot || parsed.pathname.startsWith(loginRoot + '/')) {
27
+ return '/';
28
+ }
29
+ const normalized = parsed.pathname + parsed.search + parsed.hash;
30
+ if (!normalized.startsWith('/') || normalized.startsWith('//') || normalized.includes('\\')) {
31
+ return '/';
32
+ }
33
+ return normalized;
34
+ }
35
+ catch {
36
+ return '/';
37
+ }
38
+ };
39
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5yZXR1cm5wYXRoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfcmVseWluZ3BhcnR5L2hlbHBlcnMucmV0dXJucGF0aC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxzRkFBc0Y7QUFDdEYsTUFBTSxzQkFBc0IsR0FBRyxLQUFLLENBQUM7QUFFckM7Ozs7Ozs7R0FPRztBQUNILE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLENBQ2hDLGFBQXNCLEVBQ3RCLGVBQXVCLEVBQ3ZCLGtCQUEwQixFQUNsQixFQUFFO0lBQ1YsTUFBTSxTQUFTLEdBQUcsT0FBTyxhQUFhLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNoRixJQUNFLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUM7V0FDdkIsU0FBUyxDQUFDLE1BQU0sR0FBRyxzQkFBc0I7V0FDekMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7UUFDM0IsNkZBQTZGO1dBQzFGLFNBQVMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQzVCLENBQUM7UUFDRCxPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7SUFDRCxNQUFNLFNBQVMsR0FBRyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLGtCQUFrQixDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsa0JBQWtCLENBQUM7SUFDMUcsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLENBQUMsU0FBUyxFQUFFLGVBQWUsQ0FBQyxDQUFDO1FBQ25ELElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxlQUFlLEVBQUUsQ0FBQztZQUN0QyxPQUFPLEdBQUcsQ0FBQztRQUNiLENBQUM7UUFDRCxJQUFJLE1BQU0sQ0FBQyxRQUFRLEtBQUssU0FBUyxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLFNBQVMsR0FBRyxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ2pGLE9BQU8sR0FBRyxDQUFDO1FBQ2IsQ0FBQztRQUNELE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDO1FBQ2pFLElBQUksQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksVUFBVSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQzVGLE9BQU8sR0FBRyxDQUFDO1FBQ2IsQ0FBQztRQUNELE9BQU8sVUFBVSxDQUFDO0lBQ3BCLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7QUFDSCxDQUFDLENBQUMifQ==
@@ -0,0 +1,13 @@
1
+ export * from './interfaces.js';
2
+ export * from './classes.errors.js';
3
+ export * from './helpers.cookies.js';
4
+ export * from './helpers.crypto.js';
5
+ export * from './helpers.returnpath.js';
6
+ export * from './classes.secretbox.js';
7
+ export * from './classes.idpoidcprovider.js';
8
+ export * from './classes.rpmodels.js';
9
+ export * from './classes.relyingpartysessions.js';
10
+ export * from './classes.authorityobserver.js';
11
+ export * from './classes.relyingpartyroutes.js';
12
+ export * from './classes.actorscope.js';
13
+ export * from './classes.socketbinding.js';
@@ -0,0 +1,14 @@
1
+ export * from './interfaces.js';
2
+ export * from './classes.errors.js';
3
+ export * from './helpers.cookies.js';
4
+ export * from './helpers.crypto.js';
5
+ export * from './helpers.returnpath.js';
6
+ export * from './classes.secretbox.js';
7
+ export * from './classes.idpoidcprovider.js';
8
+ export * from './classes.rpmodels.js';
9
+ export * from './classes.relyingpartysessions.js';
10
+ export * from './classes.authorityobserver.js';
11
+ export * from './classes.relyingpartyroutes.js';
12
+ export * from './classes.actorscope.js';
13
+ export * from './classes.socketbinding.js';
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19yZWx5aW5ncGFydHkvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsbUNBQW1DLENBQUM7QUFDbEQsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLGlDQUFpQyxDQUFDO0FBQ2hELGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYyw0QkFBNEIsQ0FBQyJ9
@@ -0,0 +1,142 @@
1
+ import type { IOidcCompletion, TOidcCompletionFailureStage, TOidcTokenHint } from './classes.idpoidcprovider.js';
2
+ /** A session as the application reads it; no token and no sealed value is part of it. */
3
+ export interface IRelyingPartySession {
4
+ id: string;
5
+ /** the application's principal (user) the session acts as */
6
+ principalId: string;
7
+ createdAt: number;
8
+ lastSeenAt: number;
9
+ expiresAt: number;
10
+ /** the provider subject; absent for a session the application opened itself */
11
+ idpSubject?: string;
12
+ loginAttemptId?: string;
13
+ }
14
+ /**
15
+ * A live session with the principal it acts as. `principal` is what
16
+ * `principals.resolve` answered for it in the same read; with `isActive`
17
+ * principals it is `undefined`.
18
+ */
19
+ export interface IRelyingPartyLiveSession<TPrincipal extends object | undefined = undefined> {
20
+ session: IRelyingPartySession;
21
+ principal: TPrincipal;
22
+ }
23
+ /** A live session behind a cookie, with its principal and the CSRF token derived for it. */
24
+ export interface IResolvedRelyingPartySession<TPrincipal extends object | undefined = undefined> extends IRelyingPartyLiveSession<TPrincipal> {
25
+ csrfToken: string;
26
+ }
27
+ /** A session just opened: the answer, the CSRF token and the `Set-Cookie` value that carries it. */
28
+ export interface IRelyingPartySessionOpening {
29
+ session: IRelyingPartySession;
30
+ csrfToken: string;
31
+ sessionCookie: string;
32
+ }
33
+ /** The redirect that starts a provider login, and the cookie that binds it to this browser. */
34
+ export interface IRelyingPartyLoginStart {
35
+ authorizationUrl: URL;
36
+ intentCookie: string;
37
+ }
38
+ /** A provider access token usable right now, with the subject it belongs to. */
39
+ export interface IProviderToken {
40
+ accessToken: string;
41
+ subject: string;
42
+ /** epoch milliseconds; `0` when the provider named no lifetime */
43
+ expiresAt: number;
44
+ }
45
+ /** What an authority observer needs from the session owner. `RelyingPartySessions` implements it. */
46
+ export interface IAuthorityTokenSource {
47
+ /**
48
+ * The access token of a login session, rotated first when it is due. The
49
+ * session must belong to `expectedPrincipalIdArg`. Throws `AuthorityTokenError`.
50
+ */
51
+ liveProviderToken(sessionIdArg: string, expectedPrincipalIdArg: string): Promise<IProviderToken>;
52
+ }
53
+ /**
54
+ * The application's side of a provider login: who the verified subject is
55
+ * here. `admit` creates or updates the local principal (and whatever the
56
+ * application projects from the organization claims) and answers its id, or
57
+ * throws `RelyingPartyRefusal` to refuse the login; the provider tokens are
58
+ * then handed back and the refusal reaches the caller unchanged.
59
+ */
60
+ export interface IRelyingPartyPrincipals {
61
+ admit(completionArg: IOidcCompletion): Promise<{
62
+ principalId: string;
63
+ }>;
64
+ /** Whether a principal may act right now; a session of an inactive principal resolves to nothing. */
65
+ isActive(principalIdArg: string): Promise<boolean>;
66
+ }
67
+ /**
68
+ * Principals that answer the principal itself instead of a yes or no, so a
69
+ * request reads its user once: `resolve` answers the principal when it may act
70
+ * right now and `null` otherwise, and every live session carries the answer as
71
+ * `principal`. Give either `isActive` or `resolve`, never both.
72
+ */
73
+ export interface IRelyingPartyPrincipalResolver<TPrincipal extends object | undefined> {
74
+ admit(completionArg: IOidcCompletion): Promise<{
75
+ principalId: string;
76
+ }>;
77
+ /** The principal object when it may act right now, `null` otherwise; any answer that is not an object (including `false`) resolves the session to nothing. */
78
+ resolve(principalIdArg: string): Promise<TPrincipal | null>;
79
+ }
80
+ /**
81
+ * What `RelyingPartySessions` accepts as its principals: a resolver whose
82
+ * principal type every live session carries, or — only while that type is
83
+ * left `undefined` — the `isActive` form.
84
+ */
85
+ export type TRelyingPartyPrincipals<TPrincipal extends object | undefined> = IRelyingPartyPrincipalResolver<TPrincipal> | (undefined extends TPrincipal ? IRelyingPartyPrincipals : never);
86
+ export type TRelyingPartySessionEndReason =
87
+ /** the application or the user ended it */
88
+ 'logout'
89
+ /** the provider refused to rotate its tokens: the login is over there */
90
+ | 'refresh_token_refused'
91
+ /** its refresh token was sealed under a key this deployment no longer has */
92
+ | 'sealed_refresh_token_unreadable'
93
+ /** its access token is dead and there is nothing to rotate it with */
94
+ | 'access_token_unusable'
95
+ /** a rotation found it revoked by someone else */
96
+ | 'revoked_elsewhere';
97
+ export type TRelyingPartyEventName = 'idp_token_refresh_failed' | 'idp_token_store_failed' | 'idp_session_ended' | 'oidc_sealed_token_unreadable' | 'oidc_token_revocation_failed' | 'oidc_token_revocation_timed_out' | 'login_attempt_sweep_failed'
98
+ /** an application hook threw; the step it belonged to ran on regardless */
99
+ | 'hook_failed'
100
+ /** a session whose opening failed could not be revoked; its provider tokens went back anyway */
101
+ | 'session_discard_failed';
102
+ /** The application hooks whose failures `hook_failed` names. */
103
+ export type TRelyingPartyHookName = 'onProviderSessionOpened' | 'onSessionEnded' | 'onEvent';
104
+ /** One structured diagnostic line. It never carries a token, a cookie or a sealed value. */
105
+ export interface IRelyingPartyEvent {
106
+ event: TRelyingPartyEventName;
107
+ sessionId?: string;
108
+ reason?: string;
109
+ hint?: TOidcTokenHint;
110
+ hook?: TRelyingPartyHookName;
111
+ message?: string;
112
+ }
113
+ export interface IRelyingPartyHooks {
114
+ /**
115
+ * A provider login opened a session, linked to its attempt. Runs before the
116
+ * login answers; a throw fails the login: the session is revoked, the
117
+ * provider tokens are handed back and the original error is rethrown.
118
+ */
119
+ onProviderSessionOpened?(eventArg: {
120
+ session: IRelyingPartySession;
121
+ }): Promise<void>;
122
+ /**
123
+ * A session ended, or a caller tried to end one that was already over
124
+ * (`revokedHere: false`). Close its live connections when `revokedHere`,
125
+ * drop cached authority either way. Must not wait. A throw (or a rejected
126
+ * promise) is reported as a `hook_failed` event and never stops the end:
127
+ * the provider tokens go back regardless.
128
+ */
129
+ onSessionEnded?(eventArg: {
130
+ sessionId: string;
131
+ reason: TRelyingPartySessionEndReason;
132
+ revokedHere: boolean;
133
+ }): void;
134
+ /**
135
+ * Structured diagnostics; the default writes one JSON line to
136
+ * `console.error`. A throwing sink never interrupts the step that reported:
137
+ * the event and a `hook_failed` line go to `console.error` instead.
138
+ */
139
+ onEvent?(eventArg: IRelyingPartyEvent): void;
140
+ }
141
+ /** Where a failed login callback stopped; for the log, never for the browser. */
142
+ export type TLoginCallbackStage = TOidcCompletionFailureStage | 'attempt_persistence' | 'principal_admission' | 'session_persistence';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3JlbHlpbmdwYXJ0eS9pbnRlcmZhY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIifQ==
@@ -0,0 +1,10 @@
1
+ import * as asyncHooks from 'node:async_hooks';
2
+ import * as crypto from 'node:crypto';
3
+ export { asyncHooks, crypto };
4
+ import * as typedrequest from '@api.global/typedrequest';
5
+ import * as typedsocket from '@api.global/typedsocket';
6
+ export { typedrequest, typedsocket };
7
+ import * as nosqldb from '@lossless.org/client/nosqldb';
8
+ export { nosqldb };
9
+ import * as openidClient from 'openid-client';
10
+ export { openidClient };
@@ -0,0 +1,11 @@
1
+ import * as asyncHooks from 'node:async_hooks';
2
+ import * as crypto from 'node:crypto';
3
+ export { asyncHooks, crypto };
4
+ import * as typedrequest from '@api.global/typedrequest';
5
+ import * as typedsocket from '@api.global/typedsocket';
6
+ export { typedrequest, typedsocket };
7
+ import * as nosqldb from '@lossless.org/client/nosqldb';
8
+ export { nosqldb };
9
+ import * as openidClient from 'openid-client';
10
+ export { openidClient };
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3JlbHlpbmdwYXJ0eS9wbHVnaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxVQUFVLE1BQU0sa0JBQWtCLENBQUM7QUFDL0MsT0FBTyxLQUFLLE1BQU0sTUFBTSxhQUFhLENBQUM7QUFFdEMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsQ0FBQztBQUU5QixPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUFFLFlBQVksRUFBRSxXQUFXLEVBQUUsQ0FBQztBQUVyQyxPQUFPLEtBQUssT0FBTyxNQUFNLDhCQUE4QixDQUFDO0FBRXhELE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQztBQUVuQixPQUFPLEtBQUssWUFBWSxNQUFNLGVBQWUsQ0FBQztBQUU5QyxPQUFPLEVBQUUsWUFBWSxFQUFFLENBQUMifQ==
package/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "name": "@idp.global/sdk",
3
- "version": "18.0.1",
3
+ "version": "18.2.0",
4
4
  "private": false,
5
5
  "description": "Reusable browser and server SDK for idp.global authentication.",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": "./dist_ts/index.js",
9
9
  "./browser": "./dist_ts_browser/index.js",
10
- "./server": "./dist_ts_server/index.js"
10
+ "./server": "./dist_ts_server/index.js",
11
+ "./relyingparty": "./dist_ts_relyingparty/index.js"
11
12
  },
12
13
  "main": "dist_ts/index.js",
13
14
  "typings": "dist_ts/index.d.ts",
14
15
  "author": "Task Venture Capital GmbH",
15
16
  "license": "MIT",
16
17
  "dependencies": {
17
- "@api.global/typedrequest": "^8.0.4",
18
+ "@api.global/typedrequest": "^8.2.0",
18
19
  "@api.global/typedrequest-interfaces": "^7.1.0",
19
- "@api.global/typedsocket": "^8.1.0",
20
+ "@api.global/typedsocket": "^8.7.0",
20
21
  "@idp.global/interfaces": "^29.1.0",
21
22
  "@push.rocks/smartjson": "^6.0.1",
22
23
  "@push.rocks/smartpromise": "^4.2.4",
@@ -25,25 +26,27 @@
25
26
  "@push.rocks/smarttime": "^4.2.3",
26
27
  "@push.rocks/smarturl": "^3.1.0",
27
28
  "@push.rocks/webjwt": "^1.0.10",
28
- "@push.rocks/webstore": "^2.1.0"
29
+ "@push.rocks/webstore": "^2.1.0",
30
+ "openid-client": "^6.8.8"
29
31
  },
30
32
  "peerDependencies": {
31
33
  "@lossless.org/client": ">=1.0.0 <2.0.0"
32
34
  },
33
35
  "devDependencies": {
34
36
  "@api.global/typedserver": "^11.4.0",
35
- "@git.zone/tsbuild": "^4.5.1",
36
- "@git.zone/tsdoc": "^2.2.1",
37
- "@git.zone/tsrun": "^2.0.6",
38
- "@git.zone/tstest": "^6.1.5",
37
+ "@git.zone/tsbuild": "^5.0.0",
38
+ "@git.zone/tsdoc": "^2.2.2",
39
+ "@git.zone/tsrun": "^3.0.2",
40
+ "@git.zone/tstest": "^6.3.2",
39
41
  "@lossless.org/client": "^1.0.0",
40
42
  "@lossless.org/nosqldb": "10.5.0",
41
- "@types/node": "26.6.1"
43
+ "@types/node": "26.6.2"
42
44
  },
43
45
  "files": [
44
46
  "ts/**/*",
45
47
  "ts_browser/**/*",
46
48
  "ts_server/**/*",
49
+ "ts_relyingparty/**/*",
47
50
  "dist/**/*",
48
51
  "dist_*/**/*",
49
52
  "readme.md",