@axa-fr/oidc-client-service-worker 7.29.7 → 7.30.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 +64 -0
- package/dist/OidcServiceWorker.d.ts +2 -1
- package/dist/OidcServiceWorker.d.ts.map +1 -0
- package/dist/OidcServiceWorker.js +995 -765
- package/dist/OidcServiceWorker.js.map +1 -1
- package/dist/constants.d.ts.map +1 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/dpop.d.ts.map +1 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/oidcConfig.d.ts.map +1 -0
- package/dist/protocol.d.ts +140 -2
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +50 -2
- package/dist/protocol.js.map +1 -1
- package/dist/resource.d.ts +22 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/tokenContexts.d.ts +5 -0
- package/dist/tokenContexts.d.ts.map +1 -0
- package/dist/{src/types.d.ts → types.d.ts} +26 -1
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/codeVerifier.d.ts.map +1 -0
- package/dist/{src/utils → utils}/domains.d.ts +2 -2
- package/dist/utils/domains.d.ts.map +1 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/normalizeUrl.d.ts.map +1 -0
- package/dist/utils/serializeHeaders.d.ts.map +1 -0
- package/dist/utils/shouldBypassDestination.d.ts.map +1 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/strings.d.ts.map +1 -0
- package/dist/{src/utils → utils}/tokens.d.ts +12 -3
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/waitForValidTokens.d.ts.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/OidcServiceWorker.ts +290 -34
- package/src/__tests__/jwt.spec.ts +17 -0
- package/src/__tests__/resource.spec.ts +98 -0
- package/src/__tests__/workerResource.spec.ts +919 -0
- package/src/jwt.ts +4 -1
- package/src/oidcConfig.ts +2 -1
- package/src/protocol.ts +16 -1
- package/src/resource.ts +104 -0
- package/src/tokenContexts.ts +39 -0
- package/src/types.ts +27 -1
- package/src/utils/domains.ts +20 -2
- package/src/utils/tokens.ts +155 -8
- package/src/version.ts +1 -1
- package/dist/src/OidcServiceWorker.d.ts +0 -2
- package/dist/src/OidcServiceWorker.d.ts.map +0 -1
- package/dist/src/__tests__/jwt.spec.d.ts +0 -2
- package/dist/src/__tests__/jwt.spec.d.ts.map +0 -1
- package/dist/src/__tests__/oidcConfig.spec.d.ts +0 -2
- package/dist/src/__tests__/oidcConfig.spec.d.ts.map +0 -1
- package/dist/src/__tests__/protocol.spec.d.ts +0 -2
- package/dist/src/__tests__/protocol.spec.d.ts.map +0 -1
- package/dist/src/constants.d.ts.map +0 -1
- package/dist/src/crypto.d.ts.map +0 -1
- package/dist/src/dpop.d.ts.map +0 -1
- package/dist/src/jwt.d.ts.map +0 -1
- package/dist/src/oidcConfig.d.ts.map +0 -1
- package/dist/src/protocol.d.ts +0 -134
- package/dist/src/protocol.d.ts.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils/__tests__/codeVerifier.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/codeVerifier.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/domains.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/domains.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/normalizeUrl.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/normalizeUrl.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/serializeHeaders.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/serializeHeaders.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/shouldBypassDestination.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/shouldBypassDestination.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/strings.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/strings.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/testHelper.d.ts +0 -58
- package/dist/src/utils/__tests__/testHelper.d.ts.map +0 -1
- package/dist/src/utils/__tests__/tokens.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/tokens.spec.d.ts.map +0 -1
- package/dist/src/utils/__tests__/waitForValidTokens.spec.d.ts +0 -2
- package/dist/src/utils/__tests__/waitForValidTokens.spec.d.ts.map +0 -1
- package/dist/src/utils/codeVerifier.d.ts.map +0 -1
- package/dist/src/utils/domains.d.ts.map +0 -1
- package/dist/src/utils/index.d.ts.map +0 -1
- package/dist/src/utils/normalizeUrl.d.ts.map +0 -1
- package/dist/src/utils/serializeHeaders.d.ts.map +0 -1
- package/dist/src/utils/shouldBypassDestination.d.ts.map +0 -1
- package/dist/src/utils/sleep.d.ts.map +0 -1
- package/dist/src/utils/strings.d.ts.map +0 -1
- package/dist/src/utils/tokens.d.ts.map +0 -1
- package/dist/src/utils/waitForValidTokens.d.ts.map +0 -1
- package/dist/src/version.d.ts +0 -3
- package/dist/src/version.d.ts.map +0 -1
- /package/dist/{src/constants.d.ts → constants.d.ts} +0 -0
- /package/dist/{src/crypto.d.ts → crypto.d.ts} +0 -0
- /package/dist/{src/dpop.d.ts → dpop.d.ts} +0 -0
- /package/dist/{src/jwt.d.ts → jwt.d.ts} +0 -0
- /package/dist/{src/oidcConfig.d.ts → oidcConfig.d.ts} +0 -0
- /package/dist/{src/utils → utils}/codeVerifier.d.ts +0 -0
- /package/dist/{src/utils → utils}/index.d.ts +0 -0
- /package/dist/{src/utils → utils}/normalizeUrl.d.ts +0 -0
- /package/dist/{src/utils → utils}/serializeHeaders.d.ts +0 -0
- /package/dist/{src/utils → utils}/shouldBypassDestination.d.ts +0 -0
- /package/dist/{src/utils → utils}/sleep.d.ts +0 -0
- /package/dist/{src/utils → utils}/strings.d.ts +0 -0
- /package/dist/{src/utils → utils}/waitForValidTokens.d.ts +0 -0
package/src/jwt.ts
CHANGED
|
@@ -208,11 +208,14 @@ export const generateJwtDemonstratingProofOfPossessionAsync =
|
|
|
208
208
|
(w: any) =>
|
|
209
209
|
(demonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration) =>
|
|
210
210
|
async (jwk: any, method = 'POST', url: string, extrasClaims = {}) => {
|
|
211
|
+
const target = new URL(url, w.location?.href);
|
|
212
|
+
target.search = '';
|
|
213
|
+
target.hash = '';
|
|
211
214
|
const claims = {
|
|
212
215
|
// https://www.rfc-editor.org/rfc/rfc9449.html#name-concept
|
|
213
216
|
jti: btoa(w.crypto.randomUUID()),
|
|
214
217
|
htm: method,
|
|
215
|
-
htu:
|
|
218
|
+
htu: target.href,
|
|
216
219
|
iat: Math.round(Date.now() / 1000),
|
|
217
220
|
...extrasClaims,
|
|
218
221
|
};
|
package/src/oidcConfig.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { acceptAnyDomainToken } from './constants';
|
|
2
|
+
import { getTokenContexts } from './tokenContexts';
|
|
2
3
|
import { Database, Domain, OidcConfig, OidcServerConfiguration, TrustedDomains } from './types';
|
|
3
4
|
import { getDomains, normalizeUrl } from './utils';
|
|
4
5
|
|
|
@@ -83,7 +84,7 @@ const shouldBypassNonOidcRequest = (
|
|
|
83
84
|
|
|
84
85
|
const getMatchingOidcConfigurations = (database: Database, url: string): OidcConfig[] => {
|
|
85
86
|
const normalizedUrl = normalizeUrl(url);
|
|
86
|
-
return
|
|
87
|
+
return getTokenContexts(database).filter(config => {
|
|
87
88
|
const { oidcServerConfiguration } = config || {};
|
|
88
89
|
const { tokenEndpoint, revocationEndpoint } = oidcServerConfiguration || {};
|
|
89
90
|
|
package/src/protocol.ts
CHANGED
|
@@ -37,7 +37,18 @@ export type {
|
|
|
37
37
|
* - The `PATCH` component changes for documentation or non-behavioural
|
|
38
38
|
* tweaks.
|
|
39
39
|
*/
|
|
40
|
-
export const PROTOCOL_VERSION = '1.
|
|
40
|
+
export const PROTOCOL_VERSION = '1.1.0' as const;
|
|
41
|
+
|
|
42
|
+
export type { OidcResourceConfiguration } from './resource';
|
|
43
|
+
export {
|
|
44
|
+
getContextResource,
|
|
45
|
+
getResourceConfiguration,
|
|
46
|
+
getResourceContextName,
|
|
47
|
+
isResourceOriginManaged,
|
|
48
|
+
isResourceUrlAllowed,
|
|
49
|
+
RESOURCE_TOKEN_REQUEST_HEADER,
|
|
50
|
+
validateResourceIdentifier,
|
|
51
|
+
} from './resource';
|
|
41
52
|
|
|
42
53
|
/**
|
|
43
54
|
* Every supported service worker message type.
|
|
@@ -97,6 +108,10 @@ export interface ServiceWorkerMessage<
|
|
|
97
108
|
* extend it with additional, well-known fields (see PROTOCOL.md).
|
|
98
109
|
*/
|
|
99
110
|
export interface ServiceWorkerResponse {
|
|
111
|
+
/** Resource isolation capability provided by protocol 1.1 workers. */
|
|
112
|
+
resourceTokenManagement?: boolean;
|
|
113
|
+
/** Worker-owned context names, without token or key material. */
|
|
114
|
+
resourceContexts?: string[];
|
|
100
115
|
configurationName?: string;
|
|
101
116
|
/** Set by the SW when it cannot service the request. */
|
|
102
117
|
error?: unknown;
|
package/src/resource.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** An explicitly permitted OAuth resource and its API destinations. */
|
|
2
|
+
export interface OidcResourceConfiguration {
|
|
3
|
+
resource: string;
|
|
4
|
+
allowedUrlPrefixes: readonly string[];
|
|
5
|
+
scope?: string;
|
|
6
|
+
audiences?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Worker-only resource refresh routing hint, removed before the provider request. */
|
|
10
|
+
export const RESOURCE_TOKEN_REQUEST_HEADER = 'x-oidc-resource';
|
|
11
|
+
|
|
12
|
+
/** Builds an isolated storage/worker context without creating another OIDC client. */
|
|
13
|
+
export const getResourceContextName = (configurationName: string, resource: string): string =>
|
|
14
|
+
`${configurationName}#resource=${encodeURIComponent(resource)}`;
|
|
15
|
+
|
|
16
|
+
/** Reads the exact resource identifier from an isolated context name. */
|
|
17
|
+
export const getContextResource = (configurationName: string): string | undefined => {
|
|
18
|
+
const marker = '#resource=';
|
|
19
|
+
const index = configurationName.indexOf(marker);
|
|
20
|
+
return index < 0 ? undefined : decodeURIComponent(configurationName.slice(index + marker.length));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Validates RFC 8707 identifiers without treating them as API URLs or scopes. */
|
|
24
|
+
export const validateResourceIdentifier = (resource: string): void => {
|
|
25
|
+
if (
|
|
26
|
+
typeof resource !== 'string' ||
|
|
27
|
+
!/^[a-z][a-z\d+.-]*:/i.test(resource) ||
|
|
28
|
+
resource.includes('#') ||
|
|
29
|
+
/\s/.test(resource)
|
|
30
|
+
) {
|
|
31
|
+
throw new Error('A resource must be an absolute URI without whitespace or a fragment.');
|
|
32
|
+
}
|
|
33
|
+
new URL(resource);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getApiUrl = (value: string): URL => {
|
|
37
|
+
const url = new URL(value);
|
|
38
|
+
if (
|
|
39
|
+
(url.protocol !== 'https:' && url.protocol !== 'http:') ||
|
|
40
|
+
url.username ||
|
|
41
|
+
url.password ||
|
|
42
|
+
url.hash ||
|
|
43
|
+
/%2f|%5c/i.test(url.pathname)
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'API destinations must be HTTP(S) URLs without credentials, fragments, or encoded separators.',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return url;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getApiPrefixUrl = (value: string): URL => {
|
|
53
|
+
const prefix = getApiUrl(value);
|
|
54
|
+
if (prefix.search) throw new Error('An API URL prefix must not contain a query.');
|
|
55
|
+
return prefix;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Prevents default-token fallback anywhere on an explicitly managed API origin. */
|
|
59
|
+
export const isResourceOriginManaged = (
|
|
60
|
+
configurations: readonly OidcResourceConfiguration[] | undefined,
|
|
61
|
+
destination: string,
|
|
62
|
+
): boolean => {
|
|
63
|
+
const origin = getApiUrl(destination).origin;
|
|
64
|
+
return (
|
|
65
|
+
configurations?.some(configuration =>
|
|
66
|
+
configuration.allowedUrlPrefixes.some(value => getApiPrefixUrl(value).origin === origin),
|
|
67
|
+
) ?? false
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Matches API mappings by parsed origin and path boundaries, never by host suffix. */
|
|
72
|
+
export const isResourceUrlAllowed = (
|
|
73
|
+
configuration: OidcResourceConfiguration,
|
|
74
|
+
destination: string,
|
|
75
|
+
): boolean => {
|
|
76
|
+
const url = getApiUrl(destination);
|
|
77
|
+
return configuration.allowedUrlPrefixes.some(value => {
|
|
78
|
+
const prefix = getApiPrefixUrl(value);
|
|
79
|
+
const path = prefix.pathname.replace(/\/+$/, '');
|
|
80
|
+
return (
|
|
81
|
+
url.origin === prefix.origin &&
|
|
82
|
+
(path === '' || url.pathname === path || url.pathname.startsWith(`${path}/`))
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Validates and retrieves an explicitly registered resource. */
|
|
88
|
+
export const getResourceConfiguration = (
|
|
89
|
+
configurations: readonly OidcResourceConfiguration[] | undefined,
|
|
90
|
+
resource: string,
|
|
91
|
+
): OidcResourceConfiguration => {
|
|
92
|
+
validateResourceIdentifier(resource);
|
|
93
|
+
const matches =
|
|
94
|
+
configurations?.filter(configuration => configuration.resource === resource) ?? [];
|
|
95
|
+
if (matches.length !== 1 || matches[0].allowedUrlPrefixes.length === 0) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'The resource must be registered exactly once with permitted API URL prefixes.',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
for (const prefix of matches[0].allowedUrlPrefixes) {
|
|
101
|
+
isResourceUrlAllowed(matches[0], prefix);
|
|
102
|
+
}
|
|
103
|
+
return matches[0];
|
|
104
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getResourceConfiguration, getResourceContextName } from './resource';
|
|
2
|
+
import type { Database, OidcConfig } from './types';
|
|
3
|
+
|
|
4
|
+
export const getTokenContextEntries = (database: Database): Array<[string, OidcConfig]> =>
|
|
5
|
+
Object.entries(database).flatMap(([key, configuration]): Array<[string, OidcConfig]> => [
|
|
6
|
+
[key, configuration],
|
|
7
|
+
...Object.entries(configuration.resourceEntries ?? {}),
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const getTokenContexts = (database: Database): OidcConfig[] =>
|
|
11
|
+
getTokenContextEntries(database).map(([, configuration]) => configuration);
|
|
12
|
+
|
|
13
|
+
export const getOrCreateResourceContext = (
|
|
14
|
+
configuration: OidcConfig,
|
|
15
|
+
resource: string,
|
|
16
|
+
): OidcConfig => {
|
|
17
|
+
getResourceConfiguration(configuration.oidcConfiguration?.resources, resource);
|
|
18
|
+
configuration.resourceEntries ??= {};
|
|
19
|
+
const name = getResourceContextName(configuration.configurationName, resource);
|
|
20
|
+
configuration.resourceEntries[name] ??= {
|
|
21
|
+
...configuration,
|
|
22
|
+
configurationName: name,
|
|
23
|
+
resource,
|
|
24
|
+
parentConfiguration: configuration,
|
|
25
|
+
resourceEntries: undefined,
|
|
26
|
+
authorizationScope: undefined,
|
|
27
|
+
requestedScope: undefined,
|
|
28
|
+
demonstratingProofOfPossessionApiNonce: null,
|
|
29
|
+
refreshGrant: undefined,
|
|
30
|
+
tokens: null,
|
|
31
|
+
state: null,
|
|
32
|
+
codeVerifier: null,
|
|
33
|
+
nonce: null,
|
|
34
|
+
status: null,
|
|
35
|
+
hideAccessToken: true,
|
|
36
|
+
generation: 0,
|
|
37
|
+
};
|
|
38
|
+
return configuration.resourceEntries[name];
|
|
39
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { OidcResourceConfiguration } from './resource';
|
|
2
|
+
|
|
1
3
|
export type DomainDetails = {
|
|
2
4
|
domains?: Domain[];
|
|
3
5
|
oidcDomains?: Domain[];
|
|
@@ -42,6 +44,10 @@ export type OidcServerConfiguration = {
|
|
|
42
44
|
export type OidcConfiguration = {
|
|
43
45
|
token_renew_mode: string;
|
|
44
46
|
demonstrating_proof_of_possession: boolean;
|
|
47
|
+
client_id?: string;
|
|
48
|
+
scope?: string;
|
|
49
|
+
resource?: string | readonly string[];
|
|
50
|
+
resources?: readonly OidcResourceConfiguration[];
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
export type Status =
|
|
@@ -95,6 +101,14 @@ export type Nonce = {
|
|
|
95
101
|
} | null;
|
|
96
102
|
|
|
97
103
|
export type OidcConfig = {
|
|
104
|
+
authorizationScope?: string;
|
|
105
|
+
requestedScope?: string;
|
|
106
|
+
demonstratingProofOfPossessionApiNonce?: string | null;
|
|
107
|
+
parentConfiguration?: OidcConfig;
|
|
108
|
+
resource?: string;
|
|
109
|
+
resourceEntries?: Record<string, OidcConfig>;
|
|
110
|
+
refreshGrant?: OidcConfig;
|
|
111
|
+
generation?: number;
|
|
98
112
|
demonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration | null;
|
|
99
113
|
configurationName: string;
|
|
100
114
|
tokens: Tokens | null;
|
|
@@ -117,6 +131,7 @@ export type OidcConfig = {
|
|
|
117
131
|
};
|
|
118
132
|
|
|
119
133
|
export type IdTokenPayload = {
|
|
134
|
+
sub?: string;
|
|
120
135
|
iss: string;
|
|
121
136
|
/**
|
|
122
137
|
* (Expiration Time) Claim
|
|
@@ -130,17 +145,28 @@ export type IdTokenPayload = {
|
|
|
130
145
|
};
|
|
131
146
|
|
|
132
147
|
export type AccessTokenPayload = {
|
|
148
|
+
aud?: string | string[];
|
|
149
|
+
iss?: string;
|
|
133
150
|
exp: number;
|
|
134
151
|
sub: string;
|
|
135
152
|
iat: number;
|
|
136
153
|
};
|
|
137
154
|
|
|
138
155
|
export type Tokens = {
|
|
156
|
+
scope?: string;
|
|
157
|
+
resourceContext?: {
|
|
158
|
+
resource: string;
|
|
159
|
+
issuer: string;
|
|
160
|
+
clientId: string;
|
|
161
|
+
configurationName: string;
|
|
162
|
+
subject: string;
|
|
163
|
+
grantResource: string | null;
|
|
164
|
+
};
|
|
139
165
|
issued_at: number | string;
|
|
140
166
|
access_token: string;
|
|
141
167
|
accessTokenPayload: AccessTokenPayload | null;
|
|
142
168
|
id_token: null | string;
|
|
143
|
-
idTokenPayload
|
|
169
|
+
idTokenPayload?: IdTokenPayload | null;
|
|
144
170
|
refresh_token?: string;
|
|
145
171
|
expiresAt: number;
|
|
146
172
|
expires_in: number | string;
|
package/src/utils/domains.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { acceptAnyDomainToken, openidWellknownUrlEndWith, scriptFilename } from '../constants';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getResourceConfiguration,
|
|
4
|
+
isResourceOriginManaged,
|
|
5
|
+
isResourceUrlAllowed,
|
|
6
|
+
} from '../resource';
|
|
7
|
+
import { getTokenContextEntries } from '../tokenContexts';
|
|
8
|
+
import { Database, Domain, DomainDetails, TrustedDomains } from '../types';
|
|
3
9
|
import { normalizeUrl } from './normalizeUrl';
|
|
4
10
|
|
|
5
11
|
export function checkDomain(domains: Domain[], endpoint: string) {
|
|
@@ -45,8 +51,20 @@ export const getCurrentDatabaseDomain = (
|
|
|
45
51
|
return null;
|
|
46
52
|
}
|
|
47
53
|
const datatases = [];
|
|
48
|
-
for (const [key, currentDatabase] of
|
|
54
|
+
for (const [key, currentDatabase] of getTokenContextEntries(database)) {
|
|
49
55
|
const oidcServerConfiguration = currentDatabase.oidcServerConfiguration;
|
|
56
|
+
const resources = currentDatabase.oidcConfiguration?.resources;
|
|
57
|
+
if (currentDatabase.resource) {
|
|
58
|
+
if (!isResourceUrlAllowed(getResourceConfiguration(resources, currentDatabase.resource), url))
|
|
59
|
+
continue;
|
|
60
|
+
} else if (
|
|
61
|
+
resources?.length &&
|
|
62
|
+
(!oidcServerConfiguration?.userInfoEndpoint ||
|
|
63
|
+
url !== normalizeUrl(oidcServerConfiguration.userInfoEndpoint)) &&
|
|
64
|
+
isResourceOriginManaged(resources, url)
|
|
65
|
+
) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
50
68
|
|
|
51
69
|
if (!oidcServerConfiguration) {
|
|
52
70
|
continue;
|
package/src/utils/tokens.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable simple-import-sort/exports */
|
|
2
2
|
import { TOKEN, TokenRenewMode } from '../constants';
|
|
3
|
+
import { getResourceConfiguration } from '../resource';
|
|
3
4
|
import {
|
|
4
5
|
AccessTokenPayload,
|
|
5
6
|
IdTokenPayload,
|
|
@@ -120,7 +121,20 @@ function _hideTokens(
|
|
|
120
121
|
tokens: Tokens,
|
|
121
122
|
currentDatabaseElement: OidcConfig,
|
|
122
123
|
configurationName: string,
|
|
124
|
+
isAuthorizationCode = false,
|
|
123
125
|
) {
|
|
126
|
+
const hasNewIdToken = typeof tokens.id_token === 'string' && tokens.id_token.length > 0;
|
|
127
|
+
const parent = currentDatabaseElement.parentConfiguration;
|
|
128
|
+
if (
|
|
129
|
+
currentDatabaseElement.resource &&
|
|
130
|
+
currentDatabaseElement.refreshGrant === currentDatabaseElement &&
|
|
131
|
+
tokens.refresh_token &&
|
|
132
|
+
tokens.refresh_token === parent?.tokens?.refresh_token
|
|
133
|
+
) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'The provider reused a refresh token across separate authorization transactions; use a multi-resource authorization grant instead.',
|
|
136
|
+
);
|
|
137
|
+
}
|
|
124
138
|
if (!tokens.issued_at) {
|
|
125
139
|
const currentTimeUnixSecond = new Date().getTime() / 1000;
|
|
126
140
|
tokens.issued_at = currentTimeUnixSecond;
|
|
@@ -129,6 +143,15 @@ function _hideTokens(
|
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
const accessTokenPayload = extractTokenPayload(tokens.access_token);
|
|
146
|
+
if (
|
|
147
|
+
currentDatabaseElement.resource ||
|
|
148
|
+
currentDatabaseElement.oidcConfiguration?.resources?.length
|
|
149
|
+
) {
|
|
150
|
+
tokens.scope ??=
|
|
151
|
+
currentDatabaseElement.requestedScope ??
|
|
152
|
+
currentDatabaseElement.tokens?.scope ??
|
|
153
|
+
currentDatabaseElement.oidcConfiguration?.scope;
|
|
154
|
+
}
|
|
132
155
|
const secureTokens = {
|
|
133
156
|
...tokens,
|
|
134
157
|
accessTokenPayload,
|
|
@@ -156,10 +179,24 @@ function _hideTokens(
|
|
|
156
179
|
const keyNonce = `${TOKEN.NONCE_TOKEN}_${currentDatabaseElement.configurationName}`;
|
|
157
180
|
_idTokenPayload.nonce = keyNonce;
|
|
158
181
|
}
|
|
159
|
-
secureTokens.idTokenPayload =
|
|
182
|
+
secureTokens.idTokenPayload =
|
|
183
|
+
currentDatabaseElement.resource && !hasNewIdToken ? null : _idTokenPayload;
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
currentDatabaseElement.resource &&
|
|
187
|
+
isAuthorizationCode &&
|
|
188
|
+
(!hasNewIdToken ||
|
|
189
|
+
typeof tokens.idTokenPayload?.sub !== 'string' ||
|
|
190
|
+
!tokens.idTokenPayload.sub ||
|
|
191
|
+
(currentDatabaseElement.nonce &&
|
|
192
|
+
tokens.idTokenPayload.nonce !== currentDatabaseElement.nonce.nonce))
|
|
193
|
+
) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
'Resource authorization requires a fresh ID token with the expected subject and nonce.',
|
|
196
|
+
);
|
|
160
197
|
}
|
|
161
198
|
if (tokens.refresh_token) {
|
|
162
|
-
secureTokens.refresh_token = `${TOKEN.REFRESH_TOKEN}_${configurationName}`;
|
|
199
|
+
secureTokens.refresh_token = `${TOKEN.REFRESH_TOKEN}_${(currentDatabaseElement.refreshGrant ?? currentDatabaseElement).configurationName}`;
|
|
163
200
|
}
|
|
164
201
|
|
|
165
202
|
tokens.issued_at = extractedIssueAt(tokens, accessTokenPayload, _idTokenPayload);
|
|
@@ -175,8 +212,9 @@ function _hideTokens(
|
|
|
175
212
|
: tokens.issued_at + expireIn;
|
|
176
213
|
|
|
177
214
|
let expiresAt: number;
|
|
178
|
-
const tokenRenewMode =
|
|
179
|
-
.
|
|
215
|
+
const tokenRenewMode = currentDatabaseElement.resource
|
|
216
|
+
? TokenRenewMode.access_token_invalid
|
|
217
|
+
: (currentDatabaseElement.oidcConfiguration as OidcConfiguration).token_renew_mode;
|
|
180
218
|
if (tokenRenewMode === TokenRenewMode.access_token_invalid) {
|
|
181
219
|
expiresAt = accessTokenExpiresAt;
|
|
182
220
|
} else if (tokenRenewMode === TokenRenewMode.id_token_invalid) {
|
|
@@ -189,13 +227,63 @@ function _hideTokens(
|
|
|
189
227
|
tokens.expiresAt = expiresAt;
|
|
190
228
|
const nonce = currentDatabaseElement.nonce ? currentDatabaseElement.nonce.nonce : null;
|
|
191
229
|
const { isValid, reason } = isTokensOidcValid(
|
|
192
|
-
|
|
230
|
+
currentDatabaseElement.resource && !hasNewIdToken
|
|
231
|
+
? { ...tokens, idTokenPayload: undefined }
|
|
232
|
+
: tokens,
|
|
193
233
|
nonce as string,
|
|
194
234
|
currentDatabaseElement.oidcServerConfiguration as OidcServerConfiguration,
|
|
195
235
|
); // TODO: Type assertion, could be null.
|
|
196
236
|
if (!isValid) {
|
|
197
237
|
throw Error(`Tokens are not OpenID valid, reason: ${reason}`);
|
|
198
238
|
}
|
|
239
|
+
if (currentDatabaseElement.resource) {
|
|
240
|
+
if (
|
|
241
|
+
typeof tokens.access_token !== 'string' ||
|
|
242
|
+
!tokens.access_token ||
|
|
243
|
+
!Number.isFinite(expiresAt) ||
|
|
244
|
+
!Number.isFinite(tokens.issued_at) ||
|
|
245
|
+
(tokens.refresh_token != null &&
|
|
246
|
+
(typeof tokens.refresh_token !== 'string' || !tokens.refresh_token))
|
|
247
|
+
) {
|
|
248
|
+
throw new Error('The provider did not return a usable resource token response.');
|
|
249
|
+
}
|
|
250
|
+
const resource = getResourceConfiguration(
|
|
251
|
+
currentDatabaseElement.oidcConfiguration?.resources,
|
|
252
|
+
currentDatabaseElement.resource,
|
|
253
|
+
);
|
|
254
|
+
tokens.scope ??=
|
|
255
|
+
currentDatabaseElement.requestedScope ??
|
|
256
|
+
resource.scope ??
|
|
257
|
+
currentDatabaseElement.oidcConfiguration?.scope;
|
|
258
|
+
secureTokens.scope = tokens.scope;
|
|
259
|
+
if (
|
|
260
|
+
resource.scope &&
|
|
261
|
+
!resource.scope
|
|
262
|
+
.split(/\s+/)
|
|
263
|
+
.filter(Boolean)
|
|
264
|
+
.every(scope => (tokens.scope ?? '').split(/\s+/).includes(scope))
|
|
265
|
+
) {
|
|
266
|
+
throw new Error('The provider did not grant the required resource scopes.');
|
|
267
|
+
}
|
|
268
|
+
const parentSubject = currentDatabaseElement.parentConfiguration?.tokens?.idTokenPayload?.sub;
|
|
269
|
+
const subject = tokens.idTokenPayload?.sub ?? oldTokens?.idTokenPayload?.sub ?? parentSubject;
|
|
270
|
+
if (typeof parentSubject !== 'string' || subject !== parentSubject) {
|
|
271
|
+
throw new Error('Resource authorization must preserve the authenticated subject.');
|
|
272
|
+
}
|
|
273
|
+
if (
|
|
274
|
+
(accessTokenPayload?.iss !== undefined &&
|
|
275
|
+
accessTokenPayload.iss !== currentDatabaseElement.oidcServerConfiguration?.issuer) ||
|
|
276
|
+
(accessTokenPayload &&
|
|
277
|
+
resource.audiences &&
|
|
278
|
+
!resource.audiences.some(audience =>
|
|
279
|
+
(typeof accessTokenPayload.aud === 'string'
|
|
280
|
+
? [accessTokenPayload.aud]
|
|
281
|
+
: (accessTokenPayload.aud ?? [])
|
|
282
|
+
).includes(audience),
|
|
283
|
+
))
|
|
284
|
+
)
|
|
285
|
+
throw new Error('Resource token issuer or configured audience does not match.');
|
|
286
|
+
}
|
|
199
287
|
|
|
200
288
|
// When refresh_token is not rotated we reuse old refresh_token
|
|
201
289
|
if (oldTokens != null && 'refresh_token' in oldTokens && !('refresh_token' in tokens)) {
|
|
@@ -210,26 +298,85 @@ function _hideTokens(
|
|
|
210
298
|
}
|
|
211
299
|
|
|
212
300
|
currentDatabaseElement.status = 'LOGGED_IN';
|
|
301
|
+
if (currentDatabaseElement.resource) {
|
|
302
|
+
const grant = currentDatabaseElement.refreshGrant ?? currentDatabaseElement;
|
|
303
|
+
const subject =
|
|
304
|
+
tokens.idTokenPayload?.sub ??
|
|
305
|
+
grant.tokens?.idTokenPayload?.sub ??
|
|
306
|
+
currentDatabaseElement.parentConfiguration?.tokens?.idTokenPayload?.sub;
|
|
307
|
+
if (typeof subject !== 'string')
|
|
308
|
+
throw new Error('Resource tokens require an authenticated OIDC subject.');
|
|
309
|
+
if (grant !== currentDatabaseElement && tokens.refresh_token && grant.tokens) {
|
|
310
|
+
grant.tokens.refresh_token = tokens.refresh_token;
|
|
311
|
+
}
|
|
312
|
+
const resourceContext = {
|
|
313
|
+
resource: currentDatabaseElement.resource,
|
|
314
|
+
issuer: currentDatabaseElement.oidcServerConfiguration!.issuer,
|
|
315
|
+
clientId: currentDatabaseElement.oidcConfiguration!.client_id!,
|
|
316
|
+
configurationName: currentDatabaseElement.configurationName.split('#')[0],
|
|
317
|
+
subject,
|
|
318
|
+
grantResource: grant.resource ?? null,
|
|
319
|
+
};
|
|
320
|
+
currentDatabaseElement.tokens!.resourceContext = resourceContext;
|
|
321
|
+
secureTokens.resourceContext = resourceContext;
|
|
322
|
+
}
|
|
213
323
|
return secureTokens;
|
|
214
324
|
}
|
|
215
325
|
|
|
216
326
|
const demonstratingProofOfPossessionNonceResponseHeader = 'DPoP-Nonce';
|
|
217
|
-
function hideTokens(
|
|
327
|
+
function hideTokens(
|
|
328
|
+
currentDatabaseElement: OidcConfig,
|
|
329
|
+
isCurrent: () => boolean = () => true,
|
|
330
|
+
isGrantCurrent: () => boolean = isCurrent,
|
|
331
|
+
isAuthorizationCode = false,
|
|
332
|
+
) {
|
|
218
333
|
const configurationName = currentDatabaseElement.configurationName;
|
|
219
334
|
return (response: Response) => {
|
|
220
335
|
if (response.status !== 200) {
|
|
221
336
|
return response;
|
|
222
337
|
}
|
|
223
338
|
const newHeaders = new Headers(response.headers);
|
|
224
|
-
if (
|
|
339
|
+
if (
|
|
340
|
+
response.headers.has(demonstratingProofOfPossessionNonceResponseHeader) &&
|
|
341
|
+
isGrantCurrent()
|
|
342
|
+
) {
|
|
225
343
|
currentDatabaseElement.demonstratingProofOfPossessionNonce = response.headers.get(
|
|
226
344
|
demonstratingProofOfPossessionNonceResponseHeader,
|
|
227
345
|
);
|
|
346
|
+
if (
|
|
347
|
+
currentDatabaseElement.refreshGrant &&
|
|
348
|
+
currentDatabaseElement.refreshGrant !== currentDatabaseElement
|
|
349
|
+
) {
|
|
350
|
+
currentDatabaseElement.refreshGrant.demonstratingProofOfPossessionNonce =
|
|
351
|
+
currentDatabaseElement.demonstratingProofOfPossessionNonce;
|
|
352
|
+
}
|
|
228
353
|
newHeaders.delete(demonstratingProofOfPossessionNonceResponseHeader);
|
|
229
354
|
}
|
|
230
355
|
|
|
231
356
|
return response.json().then<Response>((tokens: Tokens) => {
|
|
232
|
-
const
|
|
357
|
+
const grant = currentDatabaseElement.refreshGrant;
|
|
358
|
+
if (
|
|
359
|
+
currentDatabaseElement.resource &&
|
|
360
|
+
grant &&
|
|
361
|
+
isGrantCurrent() &&
|
|
362
|
+
grant.tokens &&
|
|
363
|
+
typeof tokens.refresh_token === 'string' &&
|
|
364
|
+
tokens.refresh_token &&
|
|
365
|
+
(grant !== currentDatabaseElement ||
|
|
366
|
+
tokens.refresh_token !==
|
|
367
|
+
currentDatabaseElement.parentConfiguration?.tokens?.refresh_token)
|
|
368
|
+
) {
|
|
369
|
+
grant.tokens.refresh_token = tokens.refresh_token;
|
|
370
|
+
}
|
|
371
|
+
if (!isCurrent()) {
|
|
372
|
+
throw new Error('The token transaction was cleared before its response.');
|
|
373
|
+
}
|
|
374
|
+
const secureTokens = _hideTokens(
|
|
375
|
+
tokens,
|
|
376
|
+
currentDatabaseElement,
|
|
377
|
+
configurationName,
|
|
378
|
+
isAuthorizationCode,
|
|
379
|
+
);
|
|
233
380
|
const body = JSON.stringify(secureTokens);
|
|
234
381
|
return new Response(body, {
|
|
235
382
|
status: response.status,
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '7.
|
|
1
|
+
export default '7.30.0';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OidcServiceWorker.d.ts","sourceRoot":"","sources":["../../src/OidcServiceWorker.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.spec.d.ts","sourceRoot":"","sources":["../../../src/__tests__/jwt.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oidcConfig.spec.d.ts","sourceRoot":"","sources":["../../../src/__tests__/oidcConfig.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.spec.d.ts","sourceRoot":"","sources":["../../../src/__tests__/protocol.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,cAAc,0BAA0B,CAAC;AAC/C,QAAA,MAAM,oBAAoB,MAAM,CAAC;AAEjC,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,QAAA,MAAM,KAAK,EAAE,SAKZ,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,gCAAgC,EAAE,MAAM,CAAC;IAClD,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC,CAAC;AAEF,QAAA,MAAM,cAAc,EAAE,kBAIrB,CAAC;AAEF,QAAA,MAAM,yBAAyB,sCAAsC,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/src/crypto.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAEA,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,2BAQzC;AAED,wBAAgB,mCAAmC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASjF"}
|
package/dist/src/dpop.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../../src/dpop.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AASnD,eAAO,MAAM,oBAAoB,GAAI,eAAe,MAAM,EAAE,GAAG,aAAa,4EAa3E,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAAI,eAAe,MAAM,EAAE,GAAG,aAAa,mBAUvF,CAAC"}
|
package/dist/src/jwt.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/jwt.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,2CAA2C,EAAE,MAAM,SAAS,CAAC;AAiCtE,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,WAOjD,CAAC;AAUF,eAAO,MAAM,kDAAkD,EAAE,2CAc9D,CAAC;AAuEJ,eAAO,MAAM,GAAG;cAnEV,GAAG,MAEL,KAAK,GAAG,EACR,SAAS,GAAG,EACZ,QAAQ,GAAG,EACX,6CAA6C,2CAA2C,EACxF,sBAA0B;CA6DH,CAAC;AAqD5B,eAAO,MAAM,GAAG;oBAtBO,GAAG,MAAY,QAAG,EAAE,iBAAiB,mBAAmB;CAsB9C,CAAC;AAElC,eAAO,MAAM,gBAAgB,GAC1B,GAAG,GAAG,MAAY,sBAAsB,qBAAqB,GAAG,cAAc,iBAO9E,CAAC;AAEJ,eAAO,MAAM,8CAA8C,GACxD,GAAG,GAAG,MACN,6CAA6C,2CAA2C,MAClF,KAAK,GAAG,EAAE,0BAAe,EAAE,KAAK,MAAM,EAAE,iBAAiB,oBAuB/D,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oidcConfig.d.ts","sourceRoot":"","sources":["../../src/oidcConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAU,UAAU,EAA2B,cAAc,EAAE,MAAM,SAAS,CAAC;AAehG,QAAA,MAAM,mBAAmB,GAAI,UAAU,QAAQ,EAAE,eAAe,MAAM,KAAG,OAWxE,CAAC;AAUF,QAAA,MAAM,0BAA0B,GAC9B,UAAU,QAAQ,EAClB,eAAe,MAAM,EACrB,gBAAgB,cAAc,KAC7B,OAeF,CAAC;AAEF,QAAA,MAAM,0BAA0B,GAC9B,UAAU,QAAQ,EAClB,eAAe,MAAM,EACrB,gBAAgB,cAAc,KAC7B,OAmBF,CAAC;AAEF,QAAA,MAAM,6BAA6B,GAAI,UAAU,QAAQ,EAAE,KAAK,MAAM,KAAG,UAAU,EAWlF,CAAC;AAEF,OAAO,EACL,6BAA6B,IAAI,gCAAgC,EACjE,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,GAC3B,CAAC"}
|