@idp.global/sdk 15.0.1 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +33 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_server/classes.oidc-introspection-client.d.ts +24 -0
- package/dist_ts_server/classes.oidc-introspection-client.js +144 -0
- package/dist_ts_server/index.d.ts +1 -0
- package/dist_ts_server/index.js +2 -1
- package/package.json +8 -8
- package/readme.md +52 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_server/classes.oidc-introspection-client.ts +140 -0
- package/ts_server/index.ts +1 -0
- /package/{license → license.md} +0 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-09 - 16.1.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add confidential OIDC introspection client (server)
|
|
8
|
+
- Add a server-only OidcIntrospectionClient with Basic authentication, nonce binding, deadlines, response size limits, and sanitized errors.
|
|
9
|
+
- Validate active and inactive introspection responses against issuer, client, subject, organization, scopes, revisions, roles, and freshness.
|
|
10
|
+
- Export the client and error types from the server entrypoint and document live organization authority usage.
|
|
11
|
+
- Update @idp.global/interfaces to ^29.1.0 for introspection response types.
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- update development tooling dependencies (tooling)
|
|
16
|
+
- Bump @git.zone/tsdoc to ^2.2.0 and @git.zone/tstest to ^4.1.1
|
|
17
|
+
- Pin @types/node to 26.4.1
|
|
18
|
+
- Update packageManager to pnpm@12.2.1
|
|
19
|
+
|
|
20
|
+
## 2026-09-05 - 16.0.0
|
|
21
|
+
|
|
22
|
+
### Breaking Changes
|
|
23
|
+
|
|
24
|
+
- upgrade SmartData dependency to major 11 (smartdata)
|
|
25
|
+
- Updates @push.rocks/smartdata from ^7.2.1 to ^11.7.0.
|
|
26
|
+
- Requires applications that provide a SmartdataDb to use SmartData 11.7 or newer within major 11.
|
|
27
|
+
- Documents that existing SDK account documents and the id_1 identity index are retained without migration.
|
|
28
|
+
- Adds regression coverage to ensure login state updates preserve account identity, credentials, and optional IdP subject.
|
|
29
|
+
|
|
30
|
+
### Fixes
|
|
31
|
+
|
|
32
|
+
- update development tooling dependencies (deps-dev)
|
|
33
|
+
- Bumps @git.zone/tsrun to ^2.0.6, @git.zone/tstest to ^4.0.0, and @types/node to ^26.4.1
|
|
34
|
+
- Updates pnpm package manager from 11.11.0 to 11.25.0
|
|
35
|
+
|
|
3
36
|
## 2026-08-02 - 15.0.1
|
|
4
37
|
|
|
5
38
|
### Fixes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@idp.global/sdk',
|
|
6
|
-
version: '
|
|
6
|
+
version: '16.1.0',
|
|
7
7
|
description: 'Reusable browser and server SDK for idp.global authentication.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxpQkFBaUI7SUFDdkIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLGdFQUFnRTtDQUM5RSxDQUFBIn0=
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
export interface IOidcIntrospectionClientOptions {
|
|
3
|
+
issuer: string;
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
maxResponseBytes?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IOidcIntrospectionClientRequest {
|
|
10
|
+
token: string;
|
|
11
|
+
subject: string;
|
|
12
|
+
organizationId: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class OidcIntrospectionError extends Error {
|
|
15
|
+
readonly code: 'invalid_request' | 'invalid_response' | 'unavailable';
|
|
16
|
+
constructor(code: 'invalid_request' | 'invalid_response' | 'unavailable');
|
|
17
|
+
}
|
|
18
|
+
/** Server-only confidential client. Each result is one live observation, never a cached grant. */
|
|
19
|
+
export declare class OidcIntrospectionClient {
|
|
20
|
+
#private;
|
|
21
|
+
constructor(optionsArg: IOidcIntrospectionClientOptions);
|
|
22
|
+
introspect(requestArg: IOidcIntrospectionClientRequest): Promise<plugins.idpInterfaces.data.TOidcIntrospectionResponse>;
|
|
23
|
+
private validateResponse;
|
|
24
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
export class OidcIntrospectionError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code) {
|
|
5
|
+
super(`OIDC introspection ${code.replace(/_/g, ' ')}.`);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = 'OidcIntrospectionError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const exactString = (valueArg, maximumArg = 512) => typeof valueArg === 'string' && valueArg.length > 0 && valueArg.length <= maximumArg
|
|
11
|
+
&& valueArg.trim() === valueArg;
|
|
12
|
+
const record = (valueArg) => valueArg !== null && typeof valueArg === 'object' && !Array.isArray(valueArg);
|
|
13
|
+
const exactKeys = (valueArg, keysArg) => Object.keys(valueArg).length === keysArg.length && keysArg.every((keyArg) => Object.hasOwn(valueArg, keyArg));
|
|
14
|
+
const revision = (valueArg) => Number.isSafeInteger(valueArg) && valueArg >= 0;
|
|
15
|
+
const sortedUnique = (valuesArg) => Array.isArray(valuesArg)
|
|
16
|
+
&& valuesArg.length > 0 && valuesArg.every((valueArg, indexArg) => exactString(valueArg, 64)
|
|
17
|
+
&& (indexArg === 0 || valuesArg[indexArg - 1] < valueArg));
|
|
18
|
+
/** Server-only confidential client. Each result is one live observation, never a cached grant. */
|
|
19
|
+
export class OidcIntrospectionClient {
|
|
20
|
+
#issuer;
|
|
21
|
+
#clientId;
|
|
22
|
+
#authorization;
|
|
23
|
+
#timeoutMs;
|
|
24
|
+
#maxResponseBytes;
|
|
25
|
+
constructor(optionsArg) {
|
|
26
|
+
if (!optionsArg || !exactString(optionsArg.issuer, 2048) || !exactString(optionsArg.clientId)
|
|
27
|
+
|| !exactString(optionsArg.clientSecret, 4096))
|
|
28
|
+
throw new OidcIntrospectionError('invalid_request');
|
|
29
|
+
let issuer;
|
|
30
|
+
try {
|
|
31
|
+
issuer = new URL(optionsArg.issuer);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new OidcIntrospectionError('invalid_request');
|
|
35
|
+
}
|
|
36
|
+
const canonicalIssuer = issuer.origin + (issuer.pathname === '/' ? '' : issuer.pathname);
|
|
37
|
+
if (issuer.protocol !== 'https:' || issuer.username || issuer.password || issuer.search || issuer.hash
|
|
38
|
+
|| issuer.pathname.includes('//') || issuer.pathname.endsWith('/') && issuer.pathname !== '/'
|
|
39
|
+
|| canonicalIssuer !== optionsArg.issuer)
|
|
40
|
+
throw new OidcIntrospectionError('invalid_request');
|
|
41
|
+
this.#timeoutMs = optionsArg.timeoutMs ?? 5000;
|
|
42
|
+
this.#maxResponseBytes = optionsArg.maxResponseBytes ?? 16384;
|
|
43
|
+
if (!Number.isSafeInteger(this.#timeoutMs) || this.#timeoutMs < 1 || this.#timeoutMs > 30000
|
|
44
|
+
|| !Number.isSafeInteger(this.#maxResponseBytes) || this.#maxResponseBytes < 1 || this.#maxResponseBytes > 65536) {
|
|
45
|
+
throw new OidcIntrospectionError('invalid_request');
|
|
46
|
+
}
|
|
47
|
+
this.#issuer = canonicalIssuer;
|
|
48
|
+
this.#clientId = optionsArg.clientId;
|
|
49
|
+
// OAuth Basic components are form-encoded separately before joining with ':'.
|
|
50
|
+
const encode = (valueArg) => new URLSearchParams({ value: valueArg }).toString().slice(6);
|
|
51
|
+
this.#authorization = `Basic ${Buffer.from(`${encode(optionsArg.clientId)}:${encode(optionsArg.clientSecret)}`, 'utf8').toString('base64')}`;
|
|
52
|
+
}
|
|
53
|
+
async introspect(requestArg) {
|
|
54
|
+
if (!requestArg || !exactString(requestArg.token, 16384) || !exactString(requestArg.subject)
|
|
55
|
+
|| !exactString(requestArg.organizationId))
|
|
56
|
+
throw new OidcIntrospectionError('invalid_request');
|
|
57
|
+
// Capture bindings before crossing any asynchronous boundary.
|
|
58
|
+
const { token, subject, organizationId } = requestArg;
|
|
59
|
+
const nonce = plugins.crypto.randomBytes(32).toString('base64url');
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
let timer;
|
|
62
|
+
const deadline = new Promise((_resolveArg, rejectArg) => {
|
|
63
|
+
timer = setTimeout(() => { controller.abort(); rejectArg(new OidcIntrospectionError('unavailable')); }, this.#timeoutMs);
|
|
64
|
+
});
|
|
65
|
+
let reader;
|
|
66
|
+
try {
|
|
67
|
+
const response = await Promise.race([fetch(`${this.#issuer}/oauth/introspect`, {
|
|
68
|
+
method: 'POST', redirect: 'error', credentials: 'omit', cache: 'no-store', signal: controller.signal,
|
|
69
|
+
headers: { authorization: this.#authorization, 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },
|
|
70
|
+
body: new URLSearchParams({ token, subject, organization_id: organizationId, nonce, token_type_hint: 'access_token' }),
|
|
71
|
+
}), deadline]);
|
|
72
|
+
if (response.status !== 200)
|
|
73
|
+
throw new OidcIntrospectionError('unavailable');
|
|
74
|
+
if (response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json'
|
|
75
|
+
|| !response.headers.get('cache-control')?.toLowerCase().split(',').map((partArg) => partArg.trim()).includes('no-store')
|
|
76
|
+
|| !response.body)
|
|
77
|
+
throw new OidcIntrospectionError('invalid_response');
|
|
78
|
+
const contentLength = response.headers.get('content-length');
|
|
79
|
+
if (contentLength !== null && (!/^\d+$/.test(contentLength) || Number(contentLength) > this.#maxResponseBytes)) {
|
|
80
|
+
throw new OidcIntrospectionError('invalid_response');
|
|
81
|
+
}
|
|
82
|
+
reader = response.body.getReader();
|
|
83
|
+
const chunks = [];
|
|
84
|
+
let size = 0;
|
|
85
|
+
while (true) {
|
|
86
|
+
const part = await Promise.race([reader.read(), deadline]);
|
|
87
|
+
if (part.done)
|
|
88
|
+
break;
|
|
89
|
+
size += part.value.byteLength;
|
|
90
|
+
if (size > this.#maxResponseBytes)
|
|
91
|
+
throw new OidcIntrospectionError('invalid_response');
|
|
92
|
+
chunks.push(part.value);
|
|
93
|
+
}
|
|
94
|
+
let value;
|
|
95
|
+
try {
|
|
96
|
+
value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new OidcIntrospectionError('invalid_response');
|
|
100
|
+
}
|
|
101
|
+
return this.validateResponse(value, subject, organizationId, nonce);
|
|
102
|
+
}
|
|
103
|
+
catch (errorArg) {
|
|
104
|
+
if (errorArg instanceof OidcIntrospectionError)
|
|
105
|
+
throw errorArg;
|
|
106
|
+
// Never include request, credentials, token, transport details or body in an error.
|
|
107
|
+
throw new OidcIntrospectionError('unavailable');
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
controller.abort();
|
|
112
|
+
if (reader)
|
|
113
|
+
void reader.cancel().catch(() => undefined);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
validateResponse(valueArg, subjectArg, organizationIdArg, nonceArg) {
|
|
117
|
+
const invalid = () => new OidcIntrospectionError('invalid_response');
|
|
118
|
+
if (!record(valueArg))
|
|
119
|
+
throw invalid();
|
|
120
|
+
if (valueArg.active === false && exactKeys(valueArg, ['active']))
|
|
121
|
+
return { active: false };
|
|
122
|
+
if (valueArg.active !== true || !exactKeys(valueArg, ['active', 'token_type', 'client_id', 'scope', 'iss', 'aud', 'sub', 'exp', 'iat', 'idp_global_authority'])
|
|
123
|
+
|| valueArg.token_type !== 'Bearer' || valueArg.client_id !== this.#clientId || valueArg.aud !== this.#clientId
|
|
124
|
+
|| valueArg.iss !== this.#issuer || valueArg.sub !== subjectArg || !revision(valueArg.exp) || !revision(valueArg.iat)
|
|
125
|
+
|| valueArg.exp <= Math.floor(Date.now() / 1000) || valueArg.iat > Math.floor(Date.now() / 1000)
|
|
126
|
+
|| valueArg.exp <= valueArg.iat || typeof valueArg.scope !== 'string')
|
|
127
|
+
throw invalid();
|
|
128
|
+
const scopes = valueArg.scope.split(' ');
|
|
129
|
+
if (!sortedUnique(scopes) || !scopes.includes('openid') || !scopes.includes('organizations')
|
|
130
|
+
|| !scopes.every((scopeArg) => ['openid', 'profile', 'email', 'organizations', 'roles'].includes(scopeArg)))
|
|
131
|
+
throw invalid();
|
|
132
|
+
const authority = valueArg.idp_global_authority;
|
|
133
|
+
if (!record(authority) || !exactKeys(authority, ['version', 'nonce', 'subject_revision', 'organization_id',
|
|
134
|
+
'organization_revision', 'membership_id', 'membership_revision', 'app_connection_id', 'app_connection_revision', 'roles'])
|
|
135
|
+
|| authority.version !== 1 || authority.nonce !== nonceArg || authority.organization_id !== organizationIdArg
|
|
136
|
+
|| !revision(authority.subject_revision) || !revision(authority.organization_revision)
|
|
137
|
+
|| !revision(authority.membership_revision) || !revision(authority.app_connection_revision)
|
|
138
|
+
|| !exactString(authority.membership_id) || !exactString(authority.app_connection_id) || !sortedUnique(authority.roles)
|
|
139
|
+
|| !authority.roles.every((roleArg) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(roleArg)))
|
|
140
|
+
throw invalid();
|
|
141
|
+
return valueArg;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5vaWRjLWludHJvc3BlY3Rpb24tY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfc2VydmVyL2NsYXNzZXMub2lkYy1pbnRyb3NwZWN0aW9uLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQWdCeEMsTUFBTSxPQUFPLHNCQUF1QixTQUFRLEtBQUs7SUFDbkI7SUFBNUIsWUFBNEIsSUFBNEQ7UUFDdEYsS0FBSyxDQUFDLHNCQUFzQixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFEOUIsU0FBSSxHQUFKLElBQUksQ0FBd0Q7UUFFdEYsSUFBSSxDQUFDLElBQUksR0FBRyx3QkFBd0IsQ0FBQztJQUN2QyxDQUFDO0NBQ0Y7QUFFRCxNQUFNLFdBQVcsR0FBRyxDQUFDLFFBQWlCLEVBQUUsVUFBVSxHQUFHLEdBQUcsRUFBc0IsRUFBRSxDQUM5RSxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksUUFBUSxDQUFDLE1BQU0sSUFBSSxVQUFVO09BQ2pGLFFBQVEsQ0FBQyxJQUFJLEVBQUUsS0FBSyxRQUFRLENBQUM7QUFDbEMsTUFBTSxNQUFNLEdBQUcsQ0FBQyxRQUFpQixFQUF1QyxFQUFFLENBQ3hFLFFBQVEsS0FBSyxJQUFJLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUNoRixNQUFNLFNBQVMsR0FBRyxDQUFDLFFBQWlDLEVBQUUsT0FBaUIsRUFBRSxFQUFFLENBQ3pFLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxLQUFLLE9BQU8sQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFF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
|
|
@@ -3,4 +3,5 @@ export * from './classes.account-auth-service.js';
|
|
|
3
3
|
export * from './classes.account-doc.js';
|
|
4
4
|
export * from './classes.account-store.js';
|
|
5
5
|
export * from './classes.idp-global-server-client.js';
|
|
6
|
+
export * from './classes.oidc-introspection-client.js';
|
|
6
7
|
export * from './classes.password-hasher.js';
|
package/dist_ts_server/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export * from './classes.account-auth-service.js';
|
|
|
3
3
|
export * from './classes.account-doc.js';
|
|
4
4
|
export * from './classes.account-store.js';
|
|
5
5
|
export * from './classes.idp-global-server-client.js';
|
|
6
|
+
export * from './classes.oidc-introspection-client.js';
|
|
6
7
|
export * from './classes.password-hasher.js';
|
|
7
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19zZXJ2ZXIvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLG1DQUFtQyxDQUFDO0FBQ2xELGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLHVDQUF1QyxDQUFDO0FBQ3RELGNBQWMsd0NBQXdDLENBQUM7QUFDdkQsY0FBYyw4QkFBOEIsQ0FBQyJ9
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idp.global/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable browser and server SDK for idp.global authentication.",
|
|
6
6
|
"type": "module",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"@api.global/typedrequest": "^5.2.1",
|
|
18
18
|
"@api.global/typedrequest-interfaces": "^5.1.1",
|
|
19
19
|
"@api.global/typedsocket": "^6.0.0",
|
|
20
|
-
"@idp.global/interfaces": "^29.
|
|
21
|
-
"@push.rocks/smartdata": "^7.
|
|
20
|
+
"@idp.global/interfaces": "^29.1.0",
|
|
21
|
+
"@push.rocks/smartdata": "^11.7.0",
|
|
22
22
|
"@push.rocks/smartjson": "^6.0.1",
|
|
23
23
|
"@push.rocks/smartpromise": "^4.2.4",
|
|
24
24
|
"@push.rocks/smartrx": "^3.0.12",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@git.zone/tsbuild": "^4.4.2",
|
|
33
|
-
"@git.zone/tsdoc": "^2.
|
|
34
|
-
"@git.zone/tsrun": "^2.0.
|
|
35
|
-
"@git.zone/tstest": "^
|
|
33
|
+
"@git.zone/tsdoc": "^2.2.0",
|
|
34
|
+
"@git.zone/tsrun": "^2.0.6",
|
|
35
|
+
"@git.zone/tstest": "^4.1.1",
|
|
36
36
|
"@push.rocks/smartdb": "^2.12.2",
|
|
37
|
-
"@types/node": "
|
|
37
|
+
"@types/node": "26.4.1"
|
|
38
38
|
},
|
|
39
39
|
"files": [
|
|
40
40
|
"ts/**/*",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dist_*/**/*",
|
|
45
45
|
"readme.md",
|
|
46
46
|
"changelog.md",
|
|
47
|
-
"license"
|
|
47
|
+
"license.md"
|
|
48
48
|
],
|
|
49
49
|
"repository": {
|
|
50
50
|
"type": "git",
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @idp.global/sdk
|
|
2
2
|
|
|
3
|
-
Reusable TypeScript SDK for building against idp.global from both sides of the wire: browser apps that need SSO, JWT housekeeping, transfer-token handoff, and typed IdP requests; and server apps that need explicit account storage, local password auth,
|
|
3
|
+
Reusable TypeScript SDK for building against idp.global from both sides of the wire: browser apps that need SSO, JWT housekeeping, transfer-token handoff, and typed IdP requests; and server apps that need explicit account storage, local password auth, optional idp.global password verification, and confidential OIDC membership introspection.
|
|
4
4
|
|
|
5
5
|
The package is intentionally split by runtime. Use `@idp.global/sdk/browser` in browser bundles and `@idp.global/sdk/server` in Node.js services. The root `@idp.global/sdk` export does not expose application APIs.
|
|
6
6
|
|
|
@@ -13,7 +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
|
-
| Server | `@idp.global/sdk/server` | Smartdata-backed account store, scrypt password hashing, local/idp.global account authentication,
|
|
16
|
+
| Server | `@idp.global/sdk/server` | Smartdata-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
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
@@ -183,6 +183,11 @@ The server SDK is deliberately explicit: it never auto-creates accounts during a
|
|
|
183
183
|
|
|
184
184
|
`SmartdataAccountStore` persists accounts through `@push.rocks/smartdata` and the exported `IdpSdkAccountDoc` collection.
|
|
185
185
|
|
|
186
|
+
The server SDK requires SmartData 11.7 or newer within major 11. Applications
|
|
187
|
+
that pass a `SmartdataDb` must use the same SmartData major. Existing SDK account
|
|
188
|
+
documents and their `id_1` identity index retain their schema; this dependency
|
|
189
|
+
migration does not transform stored accounts.
|
|
190
|
+
|
|
186
191
|
```ts
|
|
187
192
|
import * as smartdata from '@push.rocks/smartdata';
|
|
188
193
|
import { SmartdataAccountStore } from '@idp.global/sdk/server';
|
|
@@ -286,6 +291,46 @@ themselves.
|
|
|
286
291
|
Verification is keyset-only. Replace the cached snapshot on every push, select
|
|
287
292
|
the verification key by the JWT header `kid`, and reject unknown key IDs.
|
|
288
293
|
|
|
294
|
+
### Live Organization Authority
|
|
295
|
+
|
|
296
|
+
`OidcIntrospectionClient` calls the public issuer's `/oauth/introspect` endpoint
|
|
297
|
+
with confidential OAuth Basic authentication. Keep the client secret and access
|
|
298
|
+
token on the server. The token must belong to that same OAuth client and include
|
|
299
|
+
`openid organizations`; use the exact subject from the validated OIDC login and
|
|
300
|
+
the canonical organization ID.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { OidcIntrospectionClient } from '@idp.global/sdk/server';
|
|
304
|
+
|
|
305
|
+
const introspection = new OidcIntrospectionClient({
|
|
306
|
+
issuer: 'https://idp.global',
|
|
307
|
+
clientId: process.env.IDP_OIDC_CLIENT_ID!,
|
|
308
|
+
clientSecret: process.env.IDP_OIDC_CLIENT_SECRET!,
|
|
309
|
+
});
|
|
310
|
+
const authority = await introspection.introspect({
|
|
311
|
+
token: serverSession.accessToken,
|
|
312
|
+
subject: serverSession.idpSubject,
|
|
313
|
+
organizationId: requestedOrganizationId,
|
|
314
|
+
});
|
|
315
|
+
if (!authority.active) throw new Error('Organization access denied.');
|
|
316
|
+
const currentRoles = authority.idp_global_authority.roles;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Every call generates a fresh nonce and validates the exact issuer, client,
|
|
320
|
+
subject, organization, response shape, revisions, and current roles. The default
|
|
321
|
+
request deadline is five seconds and the response limit is 16 KiB; redirects
|
|
322
|
+
are rejected. `OidcIntrospectionError` exposes only a sanitized `code`:
|
|
323
|
+
`invalid_request`, `invalid_response`, or `unavailable`. Deny the operation when
|
|
324
|
+
introspection fails.
|
|
325
|
+
|
|
326
|
+
An active result observes current authority while the provider holds its
|
|
327
|
+
mutation fences. It is not a cached membership grant: membership can change
|
|
328
|
+
after that observation. `exp` bounds token, session, and refresh-family expiry;
|
|
329
|
+
it does not promise membership remains valid until then. Keep the application's
|
|
330
|
+
existing login and principal binding and make a fresh observation for each
|
|
331
|
+
operation requiring live authority. The response contains no identity profile
|
|
332
|
+
or email-based organization inference.
|
|
333
|
+
|
|
289
334
|
### Server API Surface
|
|
290
335
|
|
|
291
336
|
| API | Description |
|
|
@@ -293,6 +338,8 @@ the verification key by the JWT header `kid`, and reject unknown key IDs.
|
|
|
293
338
|
| `SmartdataAccountStore` | Smartdata-backed account persistence and local password verification. |
|
|
294
339
|
| `AccountAuthService` | Existing-account authentication orchestration for `local`, `idp.global`, and `auto`. |
|
|
295
340
|
| `IdpGlobalServerClient` | Typed-socket client for idp.global password login, JWT refresh, `whoIs` lookup, backend JWT validation fetches, and JWT key/blocklist pushes. |
|
|
341
|
+
| `OidcIntrospectionClient` | Confidential server client for an exact live organization authority observation. |
|
|
342
|
+
| `OidcIntrospectionError` | Sanitized introspection failure with a stable error code. |
|
|
296
343
|
| `IdpMfaRequiredError` | Password-login error carrying the MFA challenge token and available factor methods. |
|
|
297
344
|
| `defaultIdpGlobalBaseUrl` | Default hosted server origin: `https://app.idp.global`. |
|
|
298
345
|
| `PasswordHasher` | Static `hashPassword()` and `verifyPassword()` helpers using `scrypt:v1`. |
|
|
@@ -305,6 +352,8 @@ The server export includes these TypeScript types:
|
|
|
305
352
|
|
|
306
353
|
| Type | Values or purpose |
|
|
307
354
|
| --- | --- |
|
|
355
|
+
| `IOidcIntrospectionClientOptions` | Exact HTTPS issuer, confidential client credentials, and optional deadline/response limits. |
|
|
356
|
+
| `IOidcIntrospectionClientRequest` | Server-held access token, exact subject, and canonical organization ID. |
|
|
308
357
|
| `TIdpAccountAuthSource` | `'local' | 'idp.global'` |
|
|
309
358
|
| `TIdpAccountRole` | `'admin' | 'user'` |
|
|
310
359
|
| `TIdpAccountStatus` | `'active' | 'disabled'` |
|
|
@@ -336,7 +385,7 @@ The test suite covers the server account store and auth service, browser transfe
|
|
|
336
385
|
|
|
337
386
|
## License and Legal Information
|
|
338
387
|
|
|
339
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
|
388
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license.md](./license.md) file.
|
|
340
389
|
|
|
341
390
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
342
391
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
export interface IOidcIntrospectionClientOptions {
|
|
4
|
+
issuer: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
maxResponseBytes?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IOidcIntrospectionClientRequest {
|
|
12
|
+
token: string;
|
|
13
|
+
subject: string;
|
|
14
|
+
organizationId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class OidcIntrospectionError extends Error {
|
|
18
|
+
constructor(public readonly code: 'invalid_request' | 'invalid_response' | 'unavailable') {
|
|
19
|
+
super(`OIDC introspection ${code.replace(/_/g, ' ')}.`);
|
|
20
|
+
this.name = 'OidcIntrospectionError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const exactString = (valueArg: unknown, maximumArg = 512): valueArg is string =>
|
|
25
|
+
typeof valueArg === 'string' && valueArg.length > 0 && valueArg.length <= maximumArg
|
|
26
|
+
&& valueArg.trim() === valueArg;
|
|
27
|
+
const record = (valueArg: unknown): valueArg is Record<string, unknown> =>
|
|
28
|
+
valueArg !== null && typeof valueArg === 'object' && !Array.isArray(valueArg);
|
|
29
|
+
const exactKeys = (valueArg: Record<string, unknown>, keysArg: string[]) =>
|
|
30
|
+
Object.keys(valueArg).length === keysArg.length && keysArg.every((keyArg) => Object.hasOwn(valueArg, keyArg));
|
|
31
|
+
const revision = (valueArg: unknown): valueArg is number => Number.isSafeInteger(valueArg) && (valueArg as number) >= 0;
|
|
32
|
+
const sortedUnique = (valuesArg: unknown): valuesArg is string[] => Array.isArray(valuesArg)
|
|
33
|
+
&& valuesArg.length > 0 && valuesArg.every((valueArg, indexArg) => exactString(valueArg, 64)
|
|
34
|
+
&& (indexArg === 0 || valuesArg[indexArg - 1] < valueArg));
|
|
35
|
+
|
|
36
|
+
/** Server-only confidential client. Each result is one live observation, never a cached grant. */
|
|
37
|
+
export class OidcIntrospectionClient {
|
|
38
|
+
readonly #issuer: string;
|
|
39
|
+
readonly #clientId: string;
|
|
40
|
+
readonly #authorization: string;
|
|
41
|
+
readonly #timeoutMs: number;
|
|
42
|
+
readonly #maxResponseBytes: number;
|
|
43
|
+
|
|
44
|
+
constructor(optionsArg: IOidcIntrospectionClientOptions) {
|
|
45
|
+
if (!optionsArg || !exactString(optionsArg.issuer, 2048) || !exactString(optionsArg.clientId)
|
|
46
|
+
|| !exactString(optionsArg.clientSecret, 4096)) throw new OidcIntrospectionError('invalid_request');
|
|
47
|
+
let issuer: URL;
|
|
48
|
+
try { issuer = new URL(optionsArg.issuer); } catch { throw new OidcIntrospectionError('invalid_request'); }
|
|
49
|
+
const canonicalIssuer = issuer.origin + (issuer.pathname === '/' ? '' : issuer.pathname);
|
|
50
|
+
if (issuer.protocol !== 'https:' || issuer.username || issuer.password || issuer.search || issuer.hash
|
|
51
|
+
|| issuer.pathname.includes('//') || issuer.pathname.endsWith('/') && issuer.pathname !== '/'
|
|
52
|
+
|| canonicalIssuer !== optionsArg.issuer) throw new OidcIntrospectionError('invalid_request');
|
|
53
|
+
this.#timeoutMs = optionsArg.timeoutMs ?? 5000;
|
|
54
|
+
this.#maxResponseBytes = optionsArg.maxResponseBytes ?? 16384;
|
|
55
|
+
if (!Number.isSafeInteger(this.#timeoutMs) || this.#timeoutMs < 1 || this.#timeoutMs > 30000
|
|
56
|
+
|| !Number.isSafeInteger(this.#maxResponseBytes) || this.#maxResponseBytes < 1 || this.#maxResponseBytes > 65536) {
|
|
57
|
+
throw new OidcIntrospectionError('invalid_request');
|
|
58
|
+
}
|
|
59
|
+
this.#issuer = canonicalIssuer;
|
|
60
|
+
this.#clientId = optionsArg.clientId;
|
|
61
|
+
// OAuth Basic components are form-encoded separately before joining with ':'.
|
|
62
|
+
const encode = (valueArg: string) => new URLSearchParams({ value: valueArg }).toString().slice(6);
|
|
63
|
+
this.#authorization = `Basic ${Buffer.from(`${encode(optionsArg.clientId)}:${encode(optionsArg.clientSecret)}`, 'utf8').toString('base64')}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async introspect(requestArg: IOidcIntrospectionClientRequest): Promise<plugins.idpInterfaces.data.TOidcIntrospectionResponse> {
|
|
67
|
+
if (!requestArg || !exactString(requestArg.token, 16384) || !exactString(requestArg.subject)
|
|
68
|
+
|| !exactString(requestArg.organizationId)) throw new OidcIntrospectionError('invalid_request');
|
|
69
|
+
// Capture bindings before crossing any asynchronous boundary.
|
|
70
|
+
const { token, subject, organizationId } = requestArg;
|
|
71
|
+
const nonce = plugins.crypto.randomBytes(32).toString('base64url');
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
74
|
+
const deadline = new Promise<never>((_resolveArg, rejectArg) => {
|
|
75
|
+
timer = setTimeout(() => { controller.abort(); rejectArg(new OidcIntrospectionError('unavailable')); }, this.#timeoutMs);
|
|
76
|
+
});
|
|
77
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
78
|
+
try {
|
|
79
|
+
const response = await Promise.race([fetch(`${this.#issuer}/oauth/introspect`, {
|
|
80
|
+
method: 'POST', redirect: 'error', credentials: 'omit', cache: 'no-store', signal: controller.signal,
|
|
81
|
+
headers: { authorization: this.#authorization, 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },
|
|
82
|
+
body: new URLSearchParams({ token, subject, organization_id: organizationId, nonce, token_type_hint: 'access_token' }),
|
|
83
|
+
}), deadline]);
|
|
84
|
+
if (response.status !== 200) throw new OidcIntrospectionError('unavailable');
|
|
85
|
+
if (response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json'
|
|
86
|
+
|| !response.headers.get('cache-control')?.toLowerCase().split(',').map((partArg) => partArg.trim()).includes('no-store')
|
|
87
|
+
|| !response.body) throw new OidcIntrospectionError('invalid_response');
|
|
88
|
+
const contentLength = response.headers.get('content-length');
|
|
89
|
+
if (contentLength !== null && (!/^\d+$/.test(contentLength) || Number(contentLength) > this.#maxResponseBytes)) {
|
|
90
|
+
throw new OidcIntrospectionError('invalid_response');
|
|
91
|
+
}
|
|
92
|
+
reader = response.body.getReader();
|
|
93
|
+
const chunks: Uint8Array[] = [];
|
|
94
|
+
let size = 0;
|
|
95
|
+
while (true) {
|
|
96
|
+
const part = await Promise.race([reader.read(), deadline]);
|
|
97
|
+
if (part.done) break;
|
|
98
|
+
size += part.value.byteLength;
|
|
99
|
+
if (size > this.#maxResponseBytes) throw new OidcIntrospectionError('invalid_response');
|
|
100
|
+
chunks.push(part.value);
|
|
101
|
+
}
|
|
102
|
+
let value: unknown;
|
|
103
|
+
try { value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks))); }
|
|
104
|
+
catch { throw new OidcIntrospectionError('invalid_response'); }
|
|
105
|
+
return this.validateResponse(value, subject, organizationId, nonce);
|
|
106
|
+
} catch (errorArg) {
|
|
107
|
+
if (errorArg instanceof OidcIntrospectionError) throw errorArg;
|
|
108
|
+
// Never include request, credentials, token, transport details or body in an error.
|
|
109
|
+
throw new OidcIntrospectionError('unavailable');
|
|
110
|
+
} finally {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
controller.abort();
|
|
113
|
+
if (reader) void reader.cancel().catch(() => undefined);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private validateResponse(valueArg: unknown, subjectArg: string, organizationIdArg: string, nonceArg: string): plugins.idpInterfaces.data.TOidcIntrospectionResponse {
|
|
118
|
+
const invalid = () => new OidcIntrospectionError('invalid_response');
|
|
119
|
+
if (!record(valueArg)) throw invalid();
|
|
120
|
+
if (valueArg.active === false && exactKeys(valueArg, ['active'])) return { active: false };
|
|
121
|
+
if (valueArg.active !== true || !exactKeys(valueArg,
|
|
122
|
+
['active', 'token_type', 'client_id', 'scope', 'iss', 'aud', 'sub', 'exp', 'iat', 'idp_global_authority'])
|
|
123
|
+
|| valueArg.token_type !== 'Bearer' || valueArg.client_id !== this.#clientId || valueArg.aud !== this.#clientId
|
|
124
|
+
|| valueArg.iss !== this.#issuer || valueArg.sub !== subjectArg || !revision(valueArg.exp) || !revision(valueArg.iat)
|
|
125
|
+
|| valueArg.exp <= Math.floor(Date.now() / 1000) || valueArg.iat > Math.floor(Date.now() / 1000)
|
|
126
|
+
|| valueArg.exp <= valueArg.iat || typeof valueArg.scope !== 'string') throw invalid();
|
|
127
|
+
const scopes = valueArg.scope.split(' ');
|
|
128
|
+
if (!sortedUnique(scopes) || !scopes.includes('openid') || !scopes.includes('organizations')
|
|
129
|
+
|| !scopes.every((scopeArg) => ['openid', 'profile', 'email', 'organizations', 'roles'].includes(scopeArg))) throw invalid();
|
|
130
|
+
const authority = valueArg.idp_global_authority;
|
|
131
|
+
if (!record(authority) || !exactKeys(authority, ['version', 'nonce', 'subject_revision', 'organization_id',
|
|
132
|
+
'organization_revision', 'membership_id', 'membership_revision', 'app_connection_id', 'app_connection_revision', 'roles'])
|
|
133
|
+
|| authority.version !== 1 || authority.nonce !== nonceArg || authority.organization_id !== organizationIdArg
|
|
134
|
+
|| !revision(authority.subject_revision) || !revision(authority.organization_revision)
|
|
135
|
+
|| !revision(authority.membership_revision) || !revision(authority.app_connection_revision)
|
|
136
|
+
|| !exactString(authority.membership_id) || !exactString(authority.app_connection_id) || !sortedUnique(authority.roles)
|
|
137
|
+
|| !authority.roles.every((roleArg) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(roleArg))) throw invalid();
|
|
138
|
+
return valueArg as unknown as plugins.idpInterfaces.data.IOidcActiveIntrospectionResponse;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/ts_server/index.ts
CHANGED
|
@@ -3,4 +3,5 @@ export * from './classes.account-auth-service.js';
|
|
|
3
3
|
export * from './classes.account-doc.js';
|
|
4
4
|
export * from './classes.account-store.js';
|
|
5
5
|
export * from './classes.idp-global-server-client.js';
|
|
6
|
+
export * from './classes.oidc-introspection-client.js';
|
|
6
7
|
export * from './classes.password-hasher.js';
|
/package/{license → license.md}
RENAMED
|
File without changes
|