@relyper/sp-auth 0.1.0 → 0.3.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/README.md +212 -69
- package/dist/client.d.ts +33 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +57 -4
- package/dist/client.js.map +1 -1
- package/dist/coins.d.ts +118 -0
- package/dist/coins.d.ts.map +1 -0
- package/dist/coins.js +158 -0
- package/dist/coins.js.map +1 -0
- package/dist/oidc/claims.d.ts +4 -0
- package/dist/oidc/claims.d.ts.map +1 -0
- package/dist/oidc/claims.js +50 -0
- package/dist/oidc/claims.js.map +1 -0
- package/dist/oidc/client.d.ts +44 -0
- package/dist/oidc/client.d.ts.map +1 -0
- package/dist/oidc/client.js +340 -0
- package/dist/oidc/client.js.map +1 -0
- package/dist/oidc/discovery.d.ts +15 -0
- package/dist/oidc/discovery.d.ts.map +1 -0
- package/dist/oidc/discovery.js +90 -0
- package/dist/oidc/discovery.js.map +1 -0
- package/dist/oidc/pkce.d.ts +6 -0
- package/dist/oidc/pkce.d.ts.map +1 -0
- package/dist/oidc/pkce.js +26 -0
- package/dist/oidc/pkce.js.map +1 -0
- package/dist/oidc/session.d.ts +47 -0
- package/dist/oidc/session.d.ts.map +1 -0
- package/dist/oidc/session.js +119 -0
- package/dist/oidc/session.js.map +1 -0
- package/dist/oidc/types.d.ts +130 -0
- package/dist/oidc/types.d.ts.map +1 -0
- package/dist/oidc/types.js +17 -0
- package/dist/oidc/types.js.map +1 -0
- package/dist/oidc-fastify.d.ts +123 -0
- package/dist/oidc-fastify.d.ts.map +1 -0
- package/dist/oidc-fastify.js +264 -0
- package/dist/oidc-fastify.js.map +1 -0
- package/dist/oidc.d.ts +15 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +14 -0
- package/dist/oidc.js.map +1 -0
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +25 -5
package/dist/coins.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the Relyper Coins integration API.
|
|
3
|
+
*
|
|
4
|
+
* Relyper Coins are held centrally: the wallet at the identity provider is the
|
|
5
|
+
* single source of truth for what a user can spend across every Relyper product.
|
|
6
|
+
* A service provider therefore never keeps its own balance -- it asks, and it
|
|
7
|
+
* debits, and anything it stores locally is a mirror for its own reporting.
|
|
8
|
+
*
|
|
9
|
+
* The service provider authenticates with the same client ID and secret it uses
|
|
10
|
+
* for the OIDC login, so every debit is attributable to one registered client
|
|
11
|
+
* and an operator can allow or refuse coin spending per app.
|
|
12
|
+
*/
|
|
13
|
+
export class RelyperCoinsError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
status;
|
|
16
|
+
/** Present on insufficient_funds. */
|
|
17
|
+
balanceRc;
|
|
18
|
+
requestedRc;
|
|
19
|
+
cause;
|
|
20
|
+
constructor(code, message, options = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'RelyperCoinsError';
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.status = options.status ?? 502;
|
|
25
|
+
this.balanceRc = options.balanceRc;
|
|
26
|
+
this.requestedRc = options.requestedRc;
|
|
27
|
+
this.cause = options.cause;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Derives the coins API base URL from an OIDC issuer.
|
|
32
|
+
*
|
|
33
|
+
* The Relyper IdP publishes OIDC at the issuer root but mounts its regular API
|
|
34
|
+
* under `/api`, so the two differ by exactly that segment.
|
|
35
|
+
*/
|
|
36
|
+
export function coinsBaseUrlFromIssuer(issuer) {
|
|
37
|
+
const trimmed = trimTrailingSlashes(issuer);
|
|
38
|
+
return trimmed.endsWith('/api') ? trimmed : trimmed + '/api';
|
|
39
|
+
}
|
|
40
|
+
export function createRelyperCoinsClient(options) {
|
|
41
|
+
for (const key of ['baseUrl', 'clientId', 'clientSecret']) {
|
|
42
|
+
if (!options[key] || typeof options[key] !== 'string') {
|
|
43
|
+
throw new TypeError('@relyper/sp-auth/coins: ' + key + ' is required.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
47
|
+
if (typeof doFetch !== 'function') {
|
|
48
|
+
throw new TypeError('@relyper/sp-auth/coins: no fetch implementation available.');
|
|
49
|
+
}
|
|
50
|
+
const baseUrl = trimTrailingSlashes(options.baseUrl);
|
|
51
|
+
const timeout = options.requestTimeoutMs ?? 10_000;
|
|
52
|
+
// RFC 6749 section 2.3.1: both halves are form-urlencoded before the base64.
|
|
53
|
+
const authorization = 'Basic ' + Buffer.from(encodeURIComponent(options.clientId) + ':' + encodeURIComponent(options.clientSecret), 'utf8').toString('base64');
|
|
54
|
+
async function call(path, init) {
|
|
55
|
+
const url = baseUrl + path;
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
response = await doFetch(url, {
|
|
59
|
+
method: init.method,
|
|
60
|
+
headers: {
|
|
61
|
+
authorization,
|
|
62
|
+
accept: 'application/json',
|
|
63
|
+
...(init.body === undefined ? {} : { 'content-type': 'application/json' })
|
|
64
|
+
},
|
|
65
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
66
|
+
signal: AbortSignal.timeout(timeout)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (cause) {
|
|
70
|
+
throw new RelyperCoinsError('unavailable', 'The Relyper coin service could not be reached.', { cause });
|
|
71
|
+
}
|
|
72
|
+
const payload = await readJson(response);
|
|
73
|
+
if (response.ok)
|
|
74
|
+
return payload ?? {};
|
|
75
|
+
const error = typeof payload?.error === 'string' ? payload.error : '';
|
|
76
|
+
if (response.status === 402 || error === 'insufficient_funds') {
|
|
77
|
+
throw new RelyperCoinsError('insufficient_funds', 'Not enough Relyper Coins.', {
|
|
78
|
+
status: 402,
|
|
79
|
+
balanceRc: numberOrUndefined(payload?.balanceRc),
|
|
80
|
+
requestedRc: numberOrUndefined(payload?.requestedRc)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (response.status === 403 || error === 'coins_not_enabled_for_client') {
|
|
84
|
+
throw new RelyperCoinsError('coins_not_enabled', 'This application is not allowed to spend Relyper Coins.', {
|
|
85
|
+
status: 403
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (response.status === 401) {
|
|
89
|
+
throw new RelyperCoinsError('unauthorized', 'The Relyper coin service rejected this application.', {
|
|
90
|
+
status: 401
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (response.status === 400) {
|
|
94
|
+
throw new RelyperCoinsError('invalid_request', 'The coin request was rejected as invalid.', { status: 400 });
|
|
95
|
+
}
|
|
96
|
+
throw new RelyperCoinsError('unavailable', 'The Relyper coin service answered unexpectedly.', {
|
|
97
|
+
status: response.status
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function walletPath(subject) {
|
|
101
|
+
if (!subject)
|
|
102
|
+
throw new TypeError('@relyper/sp-auth/coins: subject is required.');
|
|
103
|
+
return '/integrations/coins/user/' + encodeURIComponent(subject);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
async getWallet(subject) {
|
|
107
|
+
return (await call(walletPath(subject), { method: 'GET' }));
|
|
108
|
+
},
|
|
109
|
+
async getLedger(subject, limit = 50) {
|
|
110
|
+
const payload = await call(walletPath(subject) + '/ledger?limit=' + encodeURIComponent(String(limit)), {
|
|
111
|
+
method: 'GET'
|
|
112
|
+
});
|
|
113
|
+
return Array.isArray(payload?.items) ? payload.items : [];
|
|
114
|
+
},
|
|
115
|
+
async debit(input) {
|
|
116
|
+
if (!(input.amountRc > 0)) {
|
|
117
|
+
throw new TypeError('@relyper/sp-auth/coins: amountRc must be greater than zero.');
|
|
118
|
+
}
|
|
119
|
+
const payload = await call(walletPath(input.subject) + '/debit', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body: {
|
|
122
|
+
amountRc: input.amountRc,
|
|
123
|
+
reason: input.reason,
|
|
124
|
+
idempotencyKey: input.idempotencyKey,
|
|
125
|
+
product: input.product,
|
|
126
|
+
provider: input.provider,
|
|
127
|
+
model: input.model,
|
|
128
|
+
tenantId: input.tenantId,
|
|
129
|
+
meta: input.meta ?? {}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const { ledgerEntry, dedup, ...wallet } = payload ?? {};
|
|
133
|
+
return {
|
|
134
|
+
wallet: wallet,
|
|
135
|
+
entry: ledgerEntry ?? null,
|
|
136
|
+
deduplicated: dedup === true
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function trimTrailingSlashes(value) {
|
|
142
|
+
let end = value.length;
|
|
143
|
+
while (end > 0 && value[end - 1] === '/')
|
|
144
|
+
end -= 1;
|
|
145
|
+
return value.slice(0, end);
|
|
146
|
+
}
|
|
147
|
+
function numberOrUndefined(value) {
|
|
148
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
149
|
+
}
|
|
150
|
+
async function readJson(response) {
|
|
151
|
+
try {
|
|
152
|
+
return (await response.json());
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=coins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coins.js","sourceRoot":"","sources":["../src/coins.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../src/oidc/claims.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAcnD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAQrD;AAkBD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAcxF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps the claims of a Relyper ID token onto the identity the application sees.
|
|
3
|
+
*
|
|
4
|
+
* The IdP sends tenant information under several names for backwards
|
|
5
|
+
* compatibility (`tenant`, `tenant_id`, `tenant_ids`, `tenants`); this collapses
|
|
6
|
+
* them into one shape so service providers do not each reinvent the reading.
|
|
7
|
+
*/
|
|
8
|
+
function asString(value) {
|
|
9
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
10
|
+
}
|
|
11
|
+
export function asStringList(value) {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return value.map((item) => asString(item)).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
const single = asString(value);
|
|
16
|
+
if (!single)
|
|
17
|
+
return [];
|
|
18
|
+
// Some providers send space- or comma-separated lists in a single claim.
|
|
19
|
+
return single.split(/[\s,]+/).map((item) => item.trim()).filter(Boolean);
|
|
20
|
+
}
|
|
21
|
+
function tenantIdsFrom(claims) {
|
|
22
|
+
const explicit = asStringList(claims.tenant_ids);
|
|
23
|
+
if (explicit.length)
|
|
24
|
+
return explicit;
|
|
25
|
+
const tenants = claims.tenants;
|
|
26
|
+
if (Array.isArray(tenants)) {
|
|
27
|
+
const ids = tenants
|
|
28
|
+
.map((entry) => (entry && typeof entry === 'object' ? asString(entry.tenantId) : ''))
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
if (ids.length)
|
|
31
|
+
return ids;
|
|
32
|
+
}
|
|
33
|
+
const single = asString(claims.tenant_id) || asString(claims.tenant);
|
|
34
|
+
return single ? [single] : [];
|
|
35
|
+
}
|
|
36
|
+
export function defaultClaimsToIdentity(claims) {
|
|
37
|
+
const email = asString(claims.email);
|
|
38
|
+
const tenantIds = tenantIdsFrom(claims);
|
|
39
|
+
return {
|
|
40
|
+
subject: asString(claims.sub),
|
|
41
|
+
email,
|
|
42
|
+
displayName: asString(claims.name) || asString(claims.preferred_username) || email || 'Relyper User',
|
|
43
|
+
roles: asStringList(claims.roles),
|
|
44
|
+
tenantId: tenantIds[0] ?? null,
|
|
45
|
+
tenantIds,
|
|
46
|
+
tenantName: asString(claims.tenant_name) || null,
|
|
47
|
+
teams: asStringList(claims.teams)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../src/oidc/claims.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,yEAAyE;IACzE,OAAO,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,aAAa,CAAC,MAA+B;IACpD,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,QAAQ,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAErC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,OAAO;aAChB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAE,KAAiC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aACjH,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC;IAC7B,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAA+B;IACrE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAExC,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;QAC7B,KAAK;QACL,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,KAAK,IAAI,cAAc;QACpG,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC;QACjC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI;QAC9B,SAAS;QACT,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,IAAI;QAChD,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC;KAClC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RelyperIdentity } from '../types.js';
|
|
2
|
+
import { type OidcDiscoveryDocument, type RelyperAuthorizationRequest, type RelyperLoginResult, type RelyperLoginTransaction, type RelyperOidcOptions, type ResolvedRelyperOidcOptions } from './types.js';
|
|
3
|
+
export type AuthorizationRequestOptions = {
|
|
4
|
+
/** Local path to return to after the login. Anything else falls back to '/'. */
|
|
5
|
+
returnTo?: string;
|
|
6
|
+
/** OIDC `prompt`, e.g. 'login' to force re-authentication. */
|
|
7
|
+
prompt?: string;
|
|
8
|
+
/** OIDC `login_hint`, usually an e-mail address. */
|
|
9
|
+
loginHint?: string;
|
|
10
|
+
};
|
|
11
|
+
export type RelyperOidcClient = {
|
|
12
|
+
readonly options: ResolvedRelyperOidcOptions;
|
|
13
|
+
/** Discovery document, fetched on first use and cached afterwards. */
|
|
14
|
+
discover(): Promise<OidcDiscoveryDocument>;
|
|
15
|
+
/**
|
|
16
|
+
* Starts a login. Returns the IdP URL to redirect to plus the transaction the
|
|
17
|
+
* caller has to hand back to {@link RelyperOidcClient.completeLogin}.
|
|
18
|
+
*/
|
|
19
|
+
createAuthorizationRequest(options?: AuthorizationRequestOptions): Promise<RelyperAuthorizationRequest>;
|
|
20
|
+
/**
|
|
21
|
+
* Completes a login: checks `state`, exchanges the code, verifies the ID token
|
|
22
|
+
* and returns the identity. Throws {@link RelyperOidcError} on every failure.
|
|
23
|
+
*/
|
|
24
|
+
completeLogin(params: {
|
|
25
|
+
query: Record<string, string | string[] | undefined>;
|
|
26
|
+
transaction: RelyperLoginTransaction;
|
|
27
|
+
}): Promise<RelyperLoginResult>;
|
|
28
|
+
/** Role check, identical in semantics to the header-based variant. */
|
|
29
|
+
hasRole(identity: RelyperIdentity, role: string | string[], match?: 'any' | 'all'): boolean;
|
|
30
|
+
/** URL that ends the IdP session, or null if the IdP advertises none. */
|
|
31
|
+
endSessionUrl(options?: {
|
|
32
|
+
idTokenHint?: string;
|
|
33
|
+
postLogoutRedirectUri?: string;
|
|
34
|
+
}): Promise<string | null>;
|
|
35
|
+
};
|
|
36
|
+
export declare function createRelyperOidcClient(options: RelyperOidcOptions): RelyperOidcClient;
|
|
37
|
+
/**
|
|
38
|
+
* Only a local path is ever used as a post-login target. An absolute URL, or a
|
|
39
|
+
* protocol-relative one starting with two slashes, would turn the login route
|
|
40
|
+
* into an open redirect. Backslashes are rejected too, because some browsers
|
|
41
|
+
* normalise them to forward slashes and would follow "/\\evil.example".
|
|
42
|
+
*/
|
|
43
|
+
export declare function safeReturnTo(value: unknown, fallback?: string): string;
|
|
44
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/oidc/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInD,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAEvB,KAAK,0BAA0B,EAEhC,MAAM,YAAY,CAAC;AAapB,MAAM,MAAM,2BAA2B,GAAG;IACxC,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAC7C,sEAAsE;IACtE,QAAQ,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC3C;;;OAGG;IACH,0BAA0B,CAAC,OAAO,CAAC,EAAE,2BAA2B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACxG;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;QACrD,WAAW,EAAE,uBAAuB,CAAC;KACtC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAChC,sEAAsE;IACtE,OAAO,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;IAC5F,yEAAyE;IACzE,aAAa,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3G,CAAC;AA+BF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,kBAAkB,GAAG,iBAAiB,CA6RtF;AA2BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAM,GAAG,MAAM,CAOnE"}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { createRemoteJWKSet, customFetch, jwtVerify } from 'jose';
|
|
2
|
+
import { createDiscoveryLoader } from './discovery.js';
|
|
3
|
+
import { defaultClaimsToIdentity } from './claims.js';
|
|
4
|
+
import { codeChallengeFor, createCodeVerifier, createNonce, createState } from './pkce.js';
|
|
5
|
+
import { RelyperOidcError } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* OIDC client for a Relyper service provider.
|
|
8
|
+
*
|
|
9
|
+
* Authorization Code Flow with PKCE and a confidential client: the client secret
|
|
10
|
+
* only ever travels from this server to the IdP's token endpoint, never through
|
|
11
|
+
* the browser. Every ID token is verified against the IdP's JWKS before a single
|
|
12
|
+
* claim in it is believed.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_SCOPE = 'openid email profile roles tenant teams';
|
|
15
|
+
function resolveOptions(options) {
|
|
16
|
+
const required = ['issuer', 'clientId', 'clientSecret', 'redirectUri'];
|
|
17
|
+
for (const key of required) {
|
|
18
|
+
if (!options[key] || typeof options[key] !== 'string') {
|
|
19
|
+
throw new TypeError('@relyper/sp-auth/oidc: ' + key + ' is required.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const requiredRoles = options.requiredRole === undefined
|
|
23
|
+
? []
|
|
24
|
+
: (Array.isArray(options.requiredRole) ? options.requiredRole : [options.requiredRole]).filter(Boolean);
|
|
25
|
+
return {
|
|
26
|
+
issuer: trimTrailingSlashes(options.issuer),
|
|
27
|
+
clientId: options.clientId,
|
|
28
|
+
clientSecret: options.clientSecret,
|
|
29
|
+
redirectUri: options.redirectUri,
|
|
30
|
+
scope: options.scope ?? DEFAULT_SCOPE,
|
|
31
|
+
tokenEndpointAuthMethod: options.tokenEndpointAuthMethod ?? null,
|
|
32
|
+
requiredRoles,
|
|
33
|
+
roleMatch: options.roleMatch ?? 'any',
|
|
34
|
+
requireEmail: options.requireEmail ?? true,
|
|
35
|
+
useUserInfo: options.useUserInfo ?? false,
|
|
36
|
+
clockToleranceSeconds: options.clockToleranceSeconds ?? 60,
|
|
37
|
+
discoveryTtlMs: options.discoveryTtlMs ?? 3_600_000,
|
|
38
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 10_000
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function createRelyperOidcClient(options) {
|
|
42
|
+
const resolved = resolveOptions(options);
|
|
43
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
44
|
+
if (typeof doFetch !== 'function') {
|
|
45
|
+
throw new TypeError('@relyper/sp-auth/oidc: no fetch implementation available.');
|
|
46
|
+
}
|
|
47
|
+
const mapClaims = options.mapClaims ?? defaultClaimsToIdentity;
|
|
48
|
+
const discovery = createDiscoveryLoader({
|
|
49
|
+
issuer: resolved.issuer,
|
|
50
|
+
ttlMs: resolved.discoveryTtlMs,
|
|
51
|
+
requestTimeoutMs: resolved.requestTimeoutMs,
|
|
52
|
+
fetch: doFetch
|
|
53
|
+
});
|
|
54
|
+
// One JWKS per jwks_uri. jose caches the keys, refetches on an unknown `kid`
|
|
55
|
+
// and rate-limits those refetches, which is exactly the key-rotation behaviour
|
|
56
|
+
// we would otherwise have to build by hand.
|
|
57
|
+
const jwksCache = new Map();
|
|
58
|
+
function jwksFor(uri) {
|
|
59
|
+
let jwks = jwksCache.get(uri);
|
|
60
|
+
if (!jwks) {
|
|
61
|
+
jwks = createRemoteJWKSet(new URL(uri), {
|
|
62
|
+
timeoutDuration: resolved.requestTimeoutMs,
|
|
63
|
+
cooldownDuration: 30_000,
|
|
64
|
+
// The JWKS has to travel the same path as every other call to the IdP,
|
|
65
|
+
// so a proxy-aware or test fetch covers key retrieval as well.
|
|
66
|
+
[customFetch]: doFetch
|
|
67
|
+
});
|
|
68
|
+
jwksCache.set(uri, jwks);
|
|
69
|
+
}
|
|
70
|
+
return jwks;
|
|
71
|
+
}
|
|
72
|
+
function authMethod(document) {
|
|
73
|
+
if (resolved.tokenEndpointAuthMethod)
|
|
74
|
+
return resolved.tokenEndpointAuthMethod;
|
|
75
|
+
const supported = document.token_endpoint_auth_methods_supported ?? [];
|
|
76
|
+
if (supported.includes('client_secret_basic'))
|
|
77
|
+
return 'client_secret_basic';
|
|
78
|
+
if (supported.includes('client_secret_post'))
|
|
79
|
+
return 'client_secret_post';
|
|
80
|
+
// The OIDC default for a confidential client when the document says nothing.
|
|
81
|
+
return 'client_secret_basic';
|
|
82
|
+
}
|
|
83
|
+
async function exchangeCode(document, code, codeVerifier) {
|
|
84
|
+
const body = new URLSearchParams({
|
|
85
|
+
grant_type: 'authorization_code',
|
|
86
|
+
code,
|
|
87
|
+
redirect_uri: resolved.redirectUri,
|
|
88
|
+
code_verifier: codeVerifier
|
|
89
|
+
});
|
|
90
|
+
const headers = {
|
|
91
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
92
|
+
accept: 'application/json'
|
|
93
|
+
};
|
|
94
|
+
if (authMethod(document) === 'client_secret_basic') {
|
|
95
|
+
// RFC 6749 section 2.3.1: both halves are form-urlencoded before the
|
|
96
|
+
// base64. Skipping that breaks any secret holding a reserved character.
|
|
97
|
+
const credentials = encodeURIComponent(resolved.clientId) + ':' + encodeURIComponent(resolved.clientSecret);
|
|
98
|
+
headers.authorization = 'Basic ' + Buffer.from(credentials, 'utf8').toString('base64');
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
body.set('client_id', resolved.clientId);
|
|
102
|
+
body.set('client_secret', resolved.clientSecret);
|
|
103
|
+
}
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await doFetch(document.token_endpoint, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers,
|
|
109
|
+
body: body.toString(),
|
|
110
|
+
signal: AbortSignal.timeout(resolved.requestTimeoutMs)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (cause) {
|
|
114
|
+
throw new RelyperOidcError('token_exchange_failed', 'The identity provider could not be reached.', { cause });
|
|
115
|
+
}
|
|
116
|
+
const payload = await readJson(response);
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
// The IdP's own error code is safe to log; the body may hold more, so only
|
|
119
|
+
// the two documented fields are carried over.
|
|
120
|
+
const error = typeof payload?.error === 'string' ? payload.error : 'HTTP ' + response.status;
|
|
121
|
+
const description = typeof payload?.error_description === 'string' ? payload.error_description : '';
|
|
122
|
+
throw new RelyperOidcError('token_exchange_failed', 'The login could not be completed.', {
|
|
123
|
+
detail: description ? error + ': ' + description : error
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const idToken = typeof payload?.id_token === 'string' ? payload.id_token : '';
|
|
127
|
+
if (!idToken) {
|
|
128
|
+
throw new RelyperOidcError('token_exchange_failed', 'The identity provider returned no ID token.', {
|
|
129
|
+
detail: 'token response without id_token'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
idToken,
|
|
134
|
+
accessToken: typeof payload?.access_token === 'string' ? payload.access_token : '',
|
|
135
|
+
tokenType: typeof payload?.token_type === 'string' ? payload.token_type : 'Bearer',
|
|
136
|
+
expiresIn: typeof payload?.expires_in === 'number' ? payload.expires_in : null,
|
|
137
|
+
refreshToken: typeof payload?.refresh_token === 'string' ? payload.refresh_token : null,
|
|
138
|
+
scope: typeof payload?.scope === 'string' ? payload.scope : null
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function verifyIdToken(document, idToken, nonce) {
|
|
142
|
+
let payload;
|
|
143
|
+
try {
|
|
144
|
+
const result = await jwtVerify(idToken, jwksFor(document.jwks_uri), {
|
|
145
|
+
issuer: document.issuer,
|
|
146
|
+
audience: resolved.clientId,
|
|
147
|
+
clockTolerance: resolved.clockToleranceSeconds,
|
|
148
|
+
// Pinning the algorithms is what stops a token downgraded to `none`, or
|
|
149
|
+
// to an HMAC signed with the provider's public key, from being accepted.
|
|
150
|
+
algorithms: ['RS256', 'RS384', 'RS512', 'PS256', 'ES256', 'ES384']
|
|
151
|
+
});
|
|
152
|
+
payload = result.payload;
|
|
153
|
+
}
|
|
154
|
+
catch (cause) {
|
|
155
|
+
throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
|
|
156
|
+
cause,
|
|
157
|
+
detail: cause instanceof Error ? cause.message : undefined
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// OpenID Connect Core 3.1.3.7 step 11: the nonce ties this token to the login
|
|
161
|
+
// this browser started. Without the check a token captured elsewhere replays.
|
|
162
|
+
if (payload.nonce !== nonce) {
|
|
163
|
+
throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
|
|
164
|
+
detail: 'nonce mismatch'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Step 12: with several audiences, `azp` has to name this client.
|
|
168
|
+
if (Array.isArray(payload.aud) && payload.aud.length > 1 && payload.azp !== resolved.clientId) {
|
|
169
|
+
throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
|
|
170
|
+
detail: 'azp does not match the client id'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (typeof payload.sub !== 'string' || !payload.sub) {
|
|
174
|
+
throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
|
|
175
|
+
detail: 'missing sub claim'
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return payload;
|
|
179
|
+
}
|
|
180
|
+
async function fetchUserInfo(document, accessToken) {
|
|
181
|
+
if (!document.userinfo_endpoint || !accessToken)
|
|
182
|
+
return {};
|
|
183
|
+
let response;
|
|
184
|
+
try {
|
|
185
|
+
response = await doFetch(document.userinfo_endpoint, {
|
|
186
|
+
method: 'GET',
|
|
187
|
+
headers: { authorization: 'Bearer ' + accessToken, accept: 'application/json' },
|
|
188
|
+
signal: AbortSignal.timeout(resolved.requestTimeoutMs)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (cause) {
|
|
192
|
+
throw new RelyperOidcError('userinfo_failed', 'The identity provider could not be reached.', { cause });
|
|
193
|
+
}
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
throw new RelyperOidcError('userinfo_failed', 'The user profile could not be read.', {
|
|
196
|
+
detail: 'HTTP ' + response.status
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return (await readJson(response)) ?? {};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
options: resolved,
|
|
203
|
+
discover: () => discovery.load(),
|
|
204
|
+
async createAuthorizationRequest(requestOptions = {}) {
|
|
205
|
+
const document = await discovery.load();
|
|
206
|
+
const transaction = {
|
|
207
|
+
state: createState(),
|
|
208
|
+
nonce: createNonce(),
|
|
209
|
+
codeVerifier: createCodeVerifier(),
|
|
210
|
+
returnTo: safeReturnTo(requestOptions.returnTo)
|
|
211
|
+
};
|
|
212
|
+
const url = new URL(document.authorization_endpoint);
|
|
213
|
+
url.searchParams.set('response_type', 'code');
|
|
214
|
+
url.searchParams.set('client_id', resolved.clientId);
|
|
215
|
+
url.searchParams.set('redirect_uri', resolved.redirectUri);
|
|
216
|
+
url.searchParams.set('scope', resolved.scope);
|
|
217
|
+
url.searchParams.set('state', transaction.state);
|
|
218
|
+
url.searchParams.set('nonce', transaction.nonce);
|
|
219
|
+
url.searchParams.set('code_challenge', codeChallengeFor(transaction.codeVerifier));
|
|
220
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
221
|
+
if (requestOptions.prompt)
|
|
222
|
+
url.searchParams.set('prompt', requestOptions.prompt);
|
|
223
|
+
if (requestOptions.loginHint)
|
|
224
|
+
url.searchParams.set('login_hint', requestOptions.loginHint);
|
|
225
|
+
return { url: url.toString(), transaction };
|
|
226
|
+
},
|
|
227
|
+
async completeLogin({ query, transaction }) {
|
|
228
|
+
const idpError = firstValue(query.error);
|
|
229
|
+
if (idpError) {
|
|
230
|
+
throw new RelyperOidcError('idp_error', 'The identity provider refused the login.', {
|
|
231
|
+
status: 401,
|
|
232
|
+
detail: [idpError, firstValue(query.error_description)].filter(Boolean).join(': ')
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const state = firstValue(query.state);
|
|
236
|
+
// A constant-time comparison would buy nothing here: whoever can measure
|
|
237
|
+
// the response already holds the cookie this is compared against.
|
|
238
|
+
if (!state || state !== transaction.state) {
|
|
239
|
+
throw new RelyperOidcError('invalid_state', 'The login could not be verified. Please try again.', {
|
|
240
|
+
status: 400,
|
|
241
|
+
detail: state ? 'state does not match the login transaction' : 'state parameter missing'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const code = firstValue(query.code);
|
|
245
|
+
if (!code) {
|
|
246
|
+
throw new RelyperOidcError('idp_error', 'The identity provider returned no authorization code.', {
|
|
247
|
+
status: 400
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const document = await discovery.load();
|
|
251
|
+
const tokens = await exchangeCode(document, code, transaction.codeVerifier);
|
|
252
|
+
const idClaims = await verifyIdToken(document, tokens.idToken, transaction.nonce);
|
|
253
|
+
let claims = { ...idClaims };
|
|
254
|
+
if (resolved.useUserInfo) {
|
|
255
|
+
const userInfo = await fetchUserInfo(document, tokens.accessToken);
|
|
256
|
+
// OpenID Connect Core 5.3.2: a UserInfo response whose `sub` differs from
|
|
257
|
+
// the ID token's must be discarded outright.
|
|
258
|
+
if (userInfo.sub && userInfo.sub !== idClaims.sub) {
|
|
259
|
+
throw new RelyperOidcError('userinfo_failed', 'The user profile could not be read.', {
|
|
260
|
+
detail: 'userinfo sub does not match the id token'
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
claims = { ...claims, ...userInfo };
|
|
264
|
+
}
|
|
265
|
+
const identity = mapClaims(claims);
|
|
266
|
+
if (resolved.requireEmail && !identity.email) {
|
|
267
|
+
throw new RelyperOidcError('missing_email', 'The identity provider supplied no e-mail address.', {
|
|
268
|
+
status: 403
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (resolved.requiredRoles.length && !matchesRoles(identity.roles, resolved.requiredRoles, resolved.roleMatch)) {
|
|
272
|
+
throw new RelyperOidcError('missing_role', 'Your account does not have access to this application.', {
|
|
273
|
+
status: 403,
|
|
274
|
+
detail: 'presented roles: ' + (identity.roles.join(', ') || '(none)')
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return { identity, claims, tokens };
|
|
278
|
+
},
|
|
279
|
+
hasRole(identity, role, match = 'any') {
|
|
280
|
+
const wanted = Array.isArray(role) ? role : [role];
|
|
281
|
+
return matchesRoles(identity.roles, wanted, match);
|
|
282
|
+
},
|
|
283
|
+
async endSessionUrl(endSessionOptions = {}) {
|
|
284
|
+
const document = await discovery.load();
|
|
285
|
+
if (!document.end_session_endpoint)
|
|
286
|
+
return null;
|
|
287
|
+
const url = new URL(document.end_session_endpoint);
|
|
288
|
+
if (endSessionOptions.idTokenHint)
|
|
289
|
+
url.searchParams.set('id_token_hint', endSessionOptions.idTokenHint);
|
|
290
|
+
if (endSessionOptions.postLogoutRedirectUri) {
|
|
291
|
+
url.searchParams.set('post_logout_redirect_uri', endSessionOptions.postLogoutRedirectUri);
|
|
292
|
+
}
|
|
293
|
+
url.searchParams.set('client_id', resolved.clientId);
|
|
294
|
+
return url.toString();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function matchesRoles(actual, required, match) {
|
|
299
|
+
return match === 'all'
|
|
300
|
+
? required.every((role) => actual.includes(role))
|
|
301
|
+
: required.some((role) => actual.includes(role));
|
|
302
|
+
}
|
|
303
|
+
function firstValue(value) {
|
|
304
|
+
if (Array.isArray(value))
|
|
305
|
+
return typeof value[0] === 'string' ? value[0] : '';
|
|
306
|
+
return typeof value === 'string' ? value : '';
|
|
307
|
+
}
|
|
308
|
+
async function readJson(response) {
|
|
309
|
+
try {
|
|
310
|
+
return (await response.json());
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function trimTrailingSlashes(value) {
|
|
317
|
+
let end = value.length;
|
|
318
|
+
while (end > 0 && value[end - 1] === '/')
|
|
319
|
+
end -= 1;
|
|
320
|
+
return value.slice(0, end);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Only a local path is ever used as a post-login target. An absolute URL, or a
|
|
324
|
+
* protocol-relative one starting with two slashes, would turn the login route
|
|
325
|
+
* into an open redirect. Backslashes are rejected too, because some browsers
|
|
326
|
+
* normalise them to forward slashes and would follow "/\\evil.example".
|
|
327
|
+
*/
|
|
328
|
+
export function safeReturnTo(value, fallback = '/') {
|
|
329
|
+
const candidate = typeof value === 'string' ? value.trim() : '';
|
|
330
|
+
if (!candidate)
|
|
331
|
+
return fallback;
|
|
332
|
+
if (!candidate.startsWith('/'))
|
|
333
|
+
return fallback;
|
|
334
|
+
if (candidate.startsWith('//'))
|
|
335
|
+
return fallback;
|
|
336
|
+
if (candidate.includes('\\'))
|
|
337
|
+
return fallback;
|
|
338
|
+
return candidate;
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/oidc/client.ts"],"names":[],"mappings":"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"}
|