oidc-spa 6.15.0 → 7.0.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 +12 -13
- package/core/Oidc.d.ts +24 -12
- package/core/createOidc.d.ts +15 -30
- package/core/createOidc.js +137 -136
- package/core/createOidc.js.map +1 -1
- package/core/handleOidcCallback.js +11 -30
- package/core/handleOidcCallback.js.map +1 -1
- package/core/loginOrGoToAuthServer.d.ts +1 -2
- package/core/loginOrGoToAuthServer.js +239 -180
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/core/loginSilent.d.ts +1 -1
- package/core/loginSilent.js +4 -4
- package/core/loginSilent.js.map +1 -1
- package/core/logoutPropagationToOtherTabs.d.ts +1 -5
- package/core/logoutPropagationToOtherTabs.js +3 -10
- package/core/logoutPropagationToOtherTabs.js.map +1 -1
- package/core/oidcClientTsUserToTokens.d.ts +1 -2
- package/core/oidcClientTsUserToTokens.js +93 -58
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/mock/oidc.d.ts +1 -1
- package/mock/oidc.js +29 -19
- package/mock/oidc.js.map +1 -1
- package/package.json +1 -5
- package/react/react.d.ts +1 -7
- package/react/react.js +8 -59
- package/react/react.js.map +1 -1
- package/src/core/Oidc.ts +27 -14
- package/src/core/createOidc.ts +124 -129
- package/src/core/handleOidcCallback.ts +12 -56
- package/src/core/loginOrGoToAuthServer.ts +26 -12
- package/src/core/loginSilent.ts +4 -4
- package/src/core/logoutPropagationToOtherTabs.ts +6 -24
- package/src/core/oidcClientTsUserToTokens.ts +129 -82
- package/src/mock/oidc.ts +16 -6
- package/src/react/react.tsx +11 -72
- package/src/tools/readExpirationTimeInJwt.ts +4 -5
- package/tools/readExpirationTimeInJwt.js +4 -4
- package/tools/readExpirationTimeInJwt.js.map +1 -1
- package/vendor/frontend/oidc-client-ts-and-jwt-decode.js +1 -1
- package/core/debug966975.d.ts +0 -7
- package/core/debug966975.js +0 -88
- package/core/debug966975.js.map +0 -1
- package/src/core/debug966975.ts +0 -85
|
@@ -8,7 +8,6 @@ import { assert, id } from "../vendor/frontend/tsafe";
|
|
|
8
8
|
import type { AuthResponse } from "./AuthResponse";
|
|
9
9
|
import { initialLocationHref } from "./initialLocationHref";
|
|
10
10
|
import { captureFetch } from "./trustedFetch";
|
|
11
|
-
import { debug966975 } from "./debug966975";
|
|
12
11
|
|
|
13
12
|
captureFetch();
|
|
14
13
|
|
|
@@ -16,19 +15,8 @@ const globalContext = {
|
|
|
16
15
|
previousCall: id<{ isHandled: boolean } | undefined>(undefined)
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
debug966975.log(
|
|
20
|
-
`=================== Evaluating the handleOidcCallback file, isInIframe: ${
|
|
21
|
-
window.self !== window.top ? "true" : "false"
|
|
22
|
-
}, location.href: ${initialLocationHref}`
|
|
23
|
-
);
|
|
24
|
-
|
|
25
18
|
export function handleOidcCallback(): { isHandled: boolean } {
|
|
26
19
|
if (globalContext.previousCall !== undefined) {
|
|
27
|
-
debug966975.log(
|
|
28
|
-
`handleOidcCallback() call, it has been called previously ${JSON.stringify(
|
|
29
|
-
globalContext.previousCall
|
|
30
|
-
)}`
|
|
31
|
-
);
|
|
32
20
|
return globalContext.previousCall;
|
|
33
21
|
}
|
|
34
22
|
|
|
@@ -36,20 +24,16 @@ export function handleOidcCallback(): { isHandled: boolean } {
|
|
|
36
24
|
}
|
|
37
25
|
|
|
38
26
|
function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
39
|
-
debug966975.log(`In handleOidcCallback_nonMemoized()`);
|
|
40
|
-
|
|
41
27
|
const location_urlObj = new URL(initialLocationHref);
|
|
42
28
|
|
|
43
29
|
const stateQueryParamValue = (() => {
|
|
44
30
|
const stateQueryParamValue = location_urlObj.searchParams.get("state");
|
|
45
31
|
|
|
46
32
|
if (stateQueryParamValue === null) {
|
|
47
|
-
debug966975.log("No state in url");
|
|
48
33
|
return undefined;
|
|
49
34
|
}
|
|
50
35
|
|
|
51
36
|
if (!getIsStatQueryParamValue({ maybeStateQueryParamValue: stateQueryParamValue })) {
|
|
52
|
-
debug966975.log(`State query param value ${stateQueryParamValue} is malformed`);
|
|
53
37
|
return undefined;
|
|
54
38
|
}
|
|
55
39
|
|
|
@@ -58,9 +42,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
58
42
|
location_urlObj.searchParams.get("response_type") !== null &&
|
|
59
43
|
location_urlObj.searchParams.get("redirect_uri") !== null
|
|
60
44
|
) {
|
|
61
|
-
debug966975.log(
|
|
62
|
-
"NOTE: We are probably in a Keycloakify theme and oidc-spa was loaded by mistake."
|
|
63
|
-
);
|
|
64
45
|
// NOTE: We are probably in a Keycloakify theme and oidc-spa was loaded by mistake.
|
|
65
46
|
return undefined;
|
|
66
47
|
}
|
|
@@ -68,13 +49,9 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
68
49
|
return stateQueryParamValue;
|
|
69
50
|
})();
|
|
70
51
|
|
|
71
|
-
debug966975.log(`state query param value ${stateQueryParamValue ?? "undefined"}`);
|
|
72
|
-
|
|
73
52
|
if (stateQueryParamValue === undefined) {
|
|
74
53
|
const backForwardTracker = readBackForwardTracker();
|
|
75
54
|
|
|
76
|
-
debug966975.log(`backForwardTracker: ${JSON.stringify(backForwardTracker)}`);
|
|
77
|
-
|
|
78
55
|
if (backForwardTracker !== undefined) {
|
|
79
56
|
writeBackForwardTracker({
|
|
80
57
|
backForwardTracker: {
|
|
@@ -84,8 +61,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
84
61
|
});
|
|
85
62
|
}
|
|
86
63
|
|
|
87
|
-
debug966975.log("returning isHandled false");
|
|
88
|
-
|
|
89
64
|
return { isHandled: false };
|
|
90
65
|
}
|
|
91
66
|
|
|
@@ -98,8 +73,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
98
73
|
|
|
99
74
|
const stateData = getStateData({ stateQueryParamValue });
|
|
100
75
|
|
|
101
|
-
debug966975.log(`stateData: ${JSON.stringify(stateData)}`);
|
|
102
|
-
|
|
103
76
|
if (
|
|
104
77
|
stateData === undefined ||
|
|
105
78
|
(stateData.context === "redirect" && stateData.hasBeenProcessedByCallback)
|
|
@@ -123,8 +96,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
123
96
|
}
|
|
124
97
|
})();
|
|
125
98
|
|
|
126
|
-
debug966975.log(`historyMethod: ${historyMethod}`);
|
|
127
|
-
|
|
128
99
|
writeBackForwardTracker({
|
|
129
100
|
backForwardTracker: {
|
|
130
101
|
previousHistoryMethod: historyMethod,
|
|
@@ -132,15 +103,20 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
132
103
|
}
|
|
133
104
|
});
|
|
134
105
|
|
|
135
|
-
reloadOnBfCacheNavigation();
|
|
136
|
-
|
|
137
106
|
setTimeout(() => {
|
|
138
|
-
|
|
107
|
+
reloadOnBfCacheNavigation();
|
|
139
108
|
|
|
140
109
|
window.history[historyMethod]();
|
|
141
|
-
}, 0);
|
|
142
110
|
|
|
143
|
-
|
|
111
|
+
// NOTE: This is a "better than nothing" approach.
|
|
112
|
+
// Under some circumstances it's possible to get stuck on this url
|
|
113
|
+
// if there is no "next" page in the history for example, navigating
|
|
114
|
+
// forward is a NoOp. So in that case it's better to navigate to the home.
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
const { protocol, host, pathname, hash } = window.location;
|
|
117
|
+
window.location.href = `${protocol}//${host}${pathname}${hash}`;
|
|
118
|
+
}, 350);
|
|
119
|
+
}, 0);
|
|
144
120
|
|
|
145
121
|
return { isHandled };
|
|
146
122
|
}
|
|
@@ -153,12 +129,9 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
153
129
|
|
|
154
130
|
assert(authResponse.state !== "", "063965");
|
|
155
131
|
|
|
156
|
-
debug966975.log(`authResponse: ${JSON.stringify(authResponse)}`);
|
|
157
|
-
|
|
158
132
|
switch (stateData.context) {
|
|
159
133
|
case "iframe":
|
|
160
134
|
setTimeout(() => {
|
|
161
|
-
debug966975.log(`(callback 0) posting message to parent`);
|
|
162
135
|
parent.postMessage(authResponse, location.origin);
|
|
163
136
|
}, 0);
|
|
164
137
|
break;
|
|
@@ -178,15 +151,11 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
|
178
151
|
return stateData.redirectUrl;
|
|
179
152
|
})();
|
|
180
153
|
|
|
181
|
-
debug966975.log(`(callback 0) location.href = "${href}";`);
|
|
182
|
-
|
|
183
154
|
location.href = href;
|
|
184
155
|
}, 0);
|
|
185
156
|
break;
|
|
186
157
|
}
|
|
187
158
|
|
|
188
|
-
debug966975.log(`Returning isHandled: ${isHandled ? "true" : "false"}`);
|
|
189
|
-
|
|
190
159
|
return { isHandled };
|
|
191
160
|
}
|
|
192
161
|
|
|
@@ -220,28 +189,18 @@ export function retrieveRedirectAuthResponseAndStateData(params: {
|
|
|
220
189
|
}): { authResponse: AuthResponse; stateData: StateData.Redirect } | undefined {
|
|
221
190
|
const { configId } = params;
|
|
222
191
|
|
|
223
|
-
debug966975.log(`>>> In retrieveRedirectAuthResponseAndStateData(${JSON.stringify({ configId })})`);
|
|
224
|
-
|
|
225
192
|
const authResponses = readRedirectAuthResponses();
|
|
226
193
|
|
|
227
|
-
debug966975.log(`authResponses: ${JSON.stringify(authResponses)}`);
|
|
228
|
-
|
|
229
194
|
let authResponseAndStateData:
|
|
230
195
|
| { authResponse: AuthResponse; stateData: StateData.Redirect }
|
|
231
196
|
| undefined = undefined;
|
|
232
197
|
|
|
233
198
|
for (const authResponse of [...authResponses]) {
|
|
234
|
-
debug966975.log(`authResponse: ${JSON.stringify(authResponse)}`);
|
|
235
|
-
|
|
236
199
|
const stateData = getStateData({ stateQueryParamValue: authResponse.state });
|
|
237
200
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
assert(stateData !== undefined, "966975");
|
|
242
|
-
} catch {
|
|
201
|
+
if (stateData === undefined) {
|
|
202
|
+
// NOTE: We do not understand how this can happen but it can.
|
|
243
203
|
authResponses.splice(authResponses.indexOf(authResponse), 1);
|
|
244
|
-
debug966975.report();
|
|
245
204
|
continue;
|
|
246
205
|
}
|
|
247
206
|
|
|
@@ -257,12 +216,9 @@ export function retrieveRedirectAuthResponseAndStateData(params: {
|
|
|
257
216
|
}
|
|
258
217
|
|
|
259
218
|
if (authResponseAndStateData !== undefined) {
|
|
260
|
-
debug966975.log(`writeRedirectAuthResponses(${JSON.stringify({ authResponses })})`);
|
|
261
219
|
writeRedirectAuthResponses({ authResponses });
|
|
262
220
|
}
|
|
263
221
|
|
|
264
|
-
debug966975.log(`Returning ${JSON.stringify({ authResponseAndStateData })} <<<<<<<<<`);
|
|
265
|
-
|
|
266
222
|
return authResponseAndStateData;
|
|
267
223
|
}
|
|
268
224
|
|
|
@@ -51,8 +51,7 @@ export function getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation() {
|
|
|
51
51
|
export function createLoginOrGoToAuthServer(params: {
|
|
52
52
|
configId: string;
|
|
53
53
|
oidcClientTsUserManager: OidcClientTsUserManager;
|
|
54
|
-
transformUrlBeforeRedirect:
|
|
55
|
-
transformUrlBeforeRedirect_next:
|
|
54
|
+
transformUrlBeforeRedirect:
|
|
56
55
|
| ((params: { authorizationUrl: string; isSilent: boolean }) => string)
|
|
57
56
|
| undefined;
|
|
58
57
|
|
|
@@ -71,7 +70,6 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
71
70
|
oidcClientTsUserManager,
|
|
72
71
|
|
|
73
72
|
transformUrlBeforeRedirect,
|
|
74
|
-
transformUrlBeforeRedirect_next,
|
|
75
73
|
getExtraQueryParams,
|
|
76
74
|
|
|
77
75
|
getExtraTokenParams,
|
|
@@ -85,11 +83,11 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
85
83
|
|
|
86
84
|
let lastPublicUrl: string | undefined = undefined;
|
|
87
85
|
|
|
88
|
-
function loginOrGoToAuthServer(params: Params): Promise<never> {
|
|
86
|
+
async function loginOrGoToAuthServer(params: Params): Promise<never> {
|
|
89
87
|
const {
|
|
90
88
|
redirectUrl: redirectUrl_params,
|
|
91
89
|
extraQueryParams_local,
|
|
92
|
-
transformUrlBeforeRedirect_local
|
|
90
|
+
transformUrlBeforeRedirect_local,
|
|
93
91
|
...rest
|
|
94
92
|
} = params;
|
|
95
93
|
|
|
@@ -107,6 +105,23 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
107
105
|
|
|
108
106
|
globalContext.evtHasLoginBeenCalled.current = true;
|
|
109
107
|
|
|
108
|
+
if (document.visibilityState !== "visible") {
|
|
109
|
+
rest.interaction === "ensure no interaction";
|
|
110
|
+
|
|
111
|
+
const dVisible = new Deferred<void>();
|
|
112
|
+
|
|
113
|
+
const onVisible = () => {
|
|
114
|
+
if (document.visibilityState !== "visible") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
118
|
+
dVisible.resolve();
|
|
119
|
+
};
|
|
120
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
121
|
+
|
|
122
|
+
await dVisible.pr;
|
|
123
|
+
}
|
|
124
|
+
|
|
110
125
|
bf_cache_handling: {
|
|
111
126
|
if (rest.doForceReloadOnBfCache) {
|
|
112
127
|
window.removeEventListener("pageshow", () => {
|
|
@@ -181,20 +196,19 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
181
196
|
(
|
|
182
197
|
[
|
|
183
198
|
[
|
|
184
|
-
|
|
185
|
-
|
|
199
|
+
getExtraQueryParams,
|
|
200
|
+
transformUrlBeforeRedirect === undefined
|
|
186
201
|
? undefined
|
|
187
202
|
: (url: string) =>
|
|
188
|
-
|
|
203
|
+
transformUrlBeforeRedirect({
|
|
189
204
|
isSilent,
|
|
190
205
|
authorizationUrl: url
|
|
191
206
|
})
|
|
192
207
|
],
|
|
193
|
-
[
|
|
194
|
-
[extraQueryParams_local, transformUrl]
|
|
208
|
+
[extraQueryParams_local, transformUrlBeforeRedirect_local]
|
|
195
209
|
] as const
|
|
196
|
-
).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i) => {
|
|
197
|
-
const url_before = i !==
|
|
210
|
+
).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i, arr) => {
|
|
211
|
+
const url_before = i !== arr.length - 1 ? undefined : url;
|
|
198
212
|
|
|
199
213
|
add_extra_query_params: {
|
|
200
214
|
if (extraQueryParamsMaybeGetter === undefined) {
|
package/src/core/loginSilent.ts
CHANGED
|
@@ -27,7 +27,7 @@ export async function loginSilent(params: {
|
|
|
27
27
|
stateQueryParamValue_instance: string;
|
|
28
28
|
configId: string;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
transformUrlBeforeRedirect:
|
|
31
31
|
| ((params: { authorizationUrl: string; isSilent: true }) => string)
|
|
32
32
|
| undefined;
|
|
33
33
|
|
|
@@ -42,7 +42,7 @@ export async function loginSilent(params: {
|
|
|
42
42
|
oidcClientTsUserManager,
|
|
43
43
|
stateQueryParamValue_instance,
|
|
44
44
|
configId,
|
|
45
|
-
|
|
45
|
+
transformUrlBeforeRedirect,
|
|
46
46
|
getExtraQueryParams,
|
|
47
47
|
getExtraTokenParams,
|
|
48
48
|
autoLogin
|
|
@@ -126,10 +126,10 @@ export async function loginSilent(params: {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
apply_transform_url: {
|
|
129
|
-
if (
|
|
129
|
+
if (transformUrlBeforeRedirect === undefined) {
|
|
130
130
|
break apply_transform_url;
|
|
131
131
|
}
|
|
132
|
-
url =
|
|
132
|
+
url = transformUrlBeforeRedirect({ authorizationUrl: url, isSilent: true });
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
return url;
|
|
@@ -7,7 +7,6 @@ const globalContext = {
|
|
|
7
7
|
|
|
8
8
|
type Message = {
|
|
9
9
|
appInstanceId: string;
|
|
10
|
-
redirectUrl_initiator: string;
|
|
11
10
|
configId: string;
|
|
12
11
|
};
|
|
13
12
|
|
|
@@ -16,15 +15,10 @@ function getChannelName(params: { sessionIdOrConfigId: string }) {
|
|
|
16
15
|
return `oidc-spa:logout-propagation:${sessionIdOrConfigId}`;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
export function notifyOtherTabsOfLogout(params: {
|
|
20
|
-
|
|
21
|
-
configId: string;
|
|
22
|
-
sessionId: string | undefined;
|
|
23
|
-
}) {
|
|
24
|
-
const { redirectUrl, configId, sessionId } = params;
|
|
18
|
+
export function notifyOtherTabsOfLogout(params: { configId: string; sessionId: string | undefined }) {
|
|
19
|
+
const { configId, sessionId } = params;
|
|
25
20
|
|
|
26
21
|
const message: Message = {
|
|
27
|
-
redirectUrl_initiator: redirectUrl,
|
|
28
22
|
configId,
|
|
29
23
|
appInstanceId: globalContext.appInstanceId
|
|
30
24
|
};
|
|
@@ -34,14 +28,10 @@ export function notifyOtherTabsOfLogout(params: {
|
|
|
34
28
|
);
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
export function getPrOtherTabLogout(params: {
|
|
38
|
-
sessionId
|
|
39
|
-
configId: string;
|
|
40
|
-
homeUrl: string;
|
|
41
|
-
}) {
|
|
42
|
-
const { sessionId, configId, homeUrl } = params;
|
|
31
|
+
export function getPrOtherTabLogout(params: { sessionId: string | undefined; configId: string }) {
|
|
32
|
+
const { sessionId, configId } = params;
|
|
43
33
|
|
|
44
|
-
const dOtherTabLogout = new Deferred<
|
|
34
|
+
const dOtherTabLogout = new Deferred<void>();
|
|
45
35
|
|
|
46
36
|
const channel = new BroadcastChannel(getChannelName({ sessionIdOrConfigId: sessionId ?? configId }));
|
|
47
37
|
|
|
@@ -54,15 +44,7 @@ export function getPrOtherTabLogout(params: {
|
|
|
54
44
|
|
|
55
45
|
channel.close();
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
if (configId === message.configId) {
|
|
59
|
-
return message.redirectUrl_initiator;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return homeUrl;
|
|
63
|
-
})();
|
|
64
|
-
|
|
65
|
-
dOtherTabLogout.resolve({ redirectUrl });
|
|
47
|
+
dOtherTabLogout.resolve();
|
|
66
48
|
};
|
|
67
49
|
|
|
68
50
|
const prOtherTabLogout = dOtherTabLogout.pr;
|
|
@@ -6,7 +6,9 @@ import type { Oidc } from "./Oidc";
|
|
|
6
6
|
|
|
7
7
|
export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, unknown>>(params: {
|
|
8
8
|
oidcClientTsUser: OidcClientTsUser;
|
|
9
|
-
decodedIdTokenSchema?: {
|
|
9
|
+
decodedIdTokenSchema?: {
|
|
10
|
+
parse: (decodedIdToken_original: Oidc.Tokens.DecodedIdToken_base) => DecodedIdToken;
|
|
11
|
+
};
|
|
10
12
|
__unsafe_useIdTokenAsAccessToken: boolean;
|
|
11
13
|
decodedIdToken_previous: DecodedIdToken | undefined;
|
|
12
14
|
log: typeof console.log | undefined;
|
|
@@ -23,69 +25,29 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
|
|
|
23
25
|
|
|
24
26
|
const accessToken = oidcClientTsUser.access_token;
|
|
25
27
|
|
|
26
|
-
const accessTokenExpirationTime = (() => {
|
|
27
|
-
read_from_token_response: {
|
|
28
|
-
const { expires_at } = oidcClientTsUser;
|
|
29
|
-
|
|
30
|
-
if (expires_at === undefined) {
|
|
31
|
-
break read_from_token_response;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return expires_at * 1000;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
read_from_jwt: {
|
|
38
|
-
const expirationTime = readExpirationTimeInJwt(accessToken);
|
|
39
|
-
|
|
40
|
-
if (expirationTime === undefined) {
|
|
41
|
-
break read_from_jwt;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return expirationTime;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
assert(false, "Failed to get access token expiration time");
|
|
48
|
-
})();
|
|
49
|
-
|
|
50
28
|
const refreshToken = oidcClientTsUser.refresh_token;
|
|
51
29
|
|
|
52
|
-
const refreshTokenExpirationTime = (() => {
|
|
53
|
-
if (refreshToken === undefined) {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
read_from_jwt: {
|
|
58
|
-
const expirationTime = readExpirationTimeInJwt(refreshToken);
|
|
59
|
-
|
|
60
|
-
if (expirationTime === undefined) {
|
|
61
|
-
break read_from_jwt;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return expirationTime;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return undefined;
|
|
68
|
-
})();
|
|
69
|
-
|
|
70
30
|
const idToken = oidcClientTsUser.id_token;
|
|
71
31
|
|
|
72
32
|
assert(idToken !== undefined, "No id token provided by the oidc server");
|
|
73
33
|
|
|
34
|
+
const decodedIdToken_original = decodeJwt<Oidc.Tokens.DecodedIdToken_base>(idToken);
|
|
35
|
+
|
|
36
|
+
if (isFirstInit) {
|
|
37
|
+
log?.(
|
|
38
|
+
[
|
|
39
|
+
`Decoded ID token`,
|
|
40
|
+
decodedIdTokenSchema === undefined ? "" : " before `decodedIdTokenSchema.parse()`\n",
|
|
41
|
+
JSON.stringify(decodedIdToken_original, null, 2)
|
|
42
|
+
].join("")
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
74
46
|
const decodedIdToken = (() => {
|
|
75
|
-
let decodedIdToken
|
|
76
|
-
|
|
77
|
-
if (isFirstInit) {
|
|
78
|
-
log?.(
|
|
79
|
-
[
|
|
80
|
-
`Decoded ID token`,
|
|
81
|
-
decodedIdTokenSchema === undefined ? "" : " before `decodedIdTokenSchema.parse()`\n",
|
|
82
|
-
JSON.stringify(decodedIdToken, null, 2)
|
|
83
|
-
].join("")
|
|
84
|
-
);
|
|
85
|
-
}
|
|
47
|
+
let decodedIdToken: DecodedIdToken;
|
|
86
48
|
|
|
87
49
|
if (decodedIdTokenSchema !== undefined) {
|
|
88
|
-
decodedIdToken = decodedIdTokenSchema.parse(
|
|
50
|
+
decodedIdToken = decodedIdTokenSchema.parse(decodedIdToken_original);
|
|
89
51
|
|
|
90
52
|
if (isFirstInit) {
|
|
91
53
|
log?.(
|
|
@@ -95,18 +57,50 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
|
|
|
95
57
|
].join("")
|
|
96
58
|
);
|
|
97
59
|
}
|
|
60
|
+
} else {
|
|
61
|
+
// @ts-expect-error
|
|
62
|
+
decodedIdToken = decodedIdToken_original;
|
|
98
63
|
}
|
|
99
64
|
|
|
100
65
|
if (
|
|
101
66
|
decodedIdToken_previous !== undefined &&
|
|
102
67
|
JSON.stringify(decodedIdToken) === JSON.stringify(decodedIdToken_previous)
|
|
103
68
|
) {
|
|
69
|
+
// NOTE: For stable ref, prevent re-render for component that would memoize
|
|
104
70
|
return decodedIdToken_previous;
|
|
105
71
|
}
|
|
106
72
|
|
|
107
73
|
return decodedIdToken;
|
|
108
74
|
})();
|
|
109
75
|
|
|
76
|
+
const issuedAtTime = (() => {
|
|
77
|
+
// NOTE: The id_token is always a JWT as per the protocol.
|
|
78
|
+
// We don't use Date.now() due to network latency.
|
|
79
|
+
const id_token_iat = (() => {
|
|
80
|
+
let iat: number | undefined;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const iat_claimValue = decodedIdToken_original.iat;
|
|
84
|
+
assert(iat_claimValue === undefined || typeof iat_claimValue === "number");
|
|
85
|
+
iat = iat_claimValue;
|
|
86
|
+
} catch {
|
|
87
|
+
iat = undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (iat === undefined) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return iat;
|
|
95
|
+
})();
|
|
96
|
+
|
|
97
|
+
if (id_token_iat === undefined) {
|
|
98
|
+
return Date.now();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return id_token_iat * 1000;
|
|
102
|
+
})();
|
|
103
|
+
|
|
110
104
|
const tokens_common: Oidc.Tokens.Common<DecodedIdToken> = {
|
|
111
105
|
...(__unsafe_useIdTokenAsAccessToken
|
|
112
106
|
? {
|
|
@@ -122,9 +116,50 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
|
|
|
122
116
|
return expirationTime;
|
|
123
117
|
})()
|
|
124
118
|
}
|
|
125
|
-
: {
|
|
119
|
+
: {
|
|
120
|
+
accessToken,
|
|
121
|
+
accessTokenExpirationTime: (() => {
|
|
122
|
+
read_from_jwt: {
|
|
123
|
+
const expirationTime = readExpirationTimeInJwt(accessToken);
|
|
124
|
+
|
|
125
|
+
if (expirationTime === undefined) {
|
|
126
|
+
break read_from_jwt;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return expirationTime;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
read_from_token_response_expires_at: {
|
|
133
|
+
const { expires_at } = oidcClientTsUser.__oidc_spa_tokenResponse;
|
|
134
|
+
|
|
135
|
+
if (expires_at === undefined) {
|
|
136
|
+
break read_from_token_response_expires_at;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
assert(typeof expires_at === "number", "2033392");
|
|
140
|
+
|
|
141
|
+
return expires_at * 1000;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
read_from_token_response_expires_in: {
|
|
145
|
+
const { expires_in } = oidcClientTsUser.__oidc_spa_tokenResponse;
|
|
146
|
+
|
|
147
|
+
if (expires_in === undefined) {
|
|
148
|
+
break read_from_token_response_expires_in;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
assert(typeof expires_in === "number", "203333425");
|
|
152
|
+
|
|
153
|
+
return issuedAtTime + expires_in * 1_000;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
assert(false, "Failed to get access token expiration time");
|
|
157
|
+
})()
|
|
158
|
+
}),
|
|
126
159
|
idToken,
|
|
127
|
-
decodedIdToken
|
|
160
|
+
decodedIdToken,
|
|
161
|
+
decodedIdToken_original,
|
|
162
|
+
issuedAtTime
|
|
128
163
|
};
|
|
129
164
|
|
|
130
165
|
const tokens: Oidc.Tokens<DecodedIdToken> =
|
|
@@ -137,7 +172,43 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
|
|
|
137
172
|
...tokens_common,
|
|
138
173
|
hasRefreshToken: true,
|
|
139
174
|
refreshToken,
|
|
140
|
-
refreshTokenExpirationTime
|
|
175
|
+
refreshTokenExpirationTime: (() => {
|
|
176
|
+
read_from_token_response_expires_at: {
|
|
177
|
+
const { refresh_expires_at } = oidcClientTsUser.__oidc_spa_tokenResponse;
|
|
178
|
+
|
|
179
|
+
if (refresh_expires_at === undefined) {
|
|
180
|
+
break read_from_token_response_expires_at;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
assert(typeof refresh_expires_at === "number", "2033392");
|
|
184
|
+
|
|
185
|
+
return refresh_expires_at * 1000;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
read_from_token_response_expires_in: {
|
|
189
|
+
const { refresh_expires_in } = oidcClientTsUser.__oidc_spa_tokenResponse;
|
|
190
|
+
|
|
191
|
+
if (refresh_expires_in === undefined) {
|
|
192
|
+
break read_from_token_response_expires_in;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
assert(typeof refresh_expires_in === "number", "2033425330");
|
|
196
|
+
|
|
197
|
+
return issuedAtTime + refresh_expires_in * 1000;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
read_from_jwt: {
|
|
201
|
+
const expirationTime = readExpirationTimeInJwt(refreshToken);
|
|
202
|
+
|
|
203
|
+
if (expirationTime === undefined) {
|
|
204
|
+
break read_from_jwt;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return expirationTime;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return undefined;
|
|
211
|
+
})()
|
|
141
212
|
});
|
|
142
213
|
|
|
143
214
|
if (
|
|
@@ -156,27 +227,3 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
|
|
|
156
227
|
|
|
157
228
|
return tokens;
|
|
158
229
|
}
|
|
159
|
-
|
|
160
|
-
export function getMsBeforeExpiration(tokens: Oidc.Tokens): number {
|
|
161
|
-
// NOTE: In general the access token is supposed to have a shorter
|
|
162
|
-
// lifespan than the refresh token but we don't want to make any
|
|
163
|
-
// assumption here.
|
|
164
|
-
const tokenExpirationTime = Math.min(
|
|
165
|
-
tokens.accessTokenExpirationTime,
|
|
166
|
-
tokens.refreshTokenExpirationTime ?? Number.POSITIVE_INFINITY
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const msBeforeExpiration = Math.min(
|
|
170
|
-
tokenExpirationTime - Date.now(),
|
|
171
|
-
// NOTE: We want to make sure we do not overflow the setTimeout
|
|
172
|
-
// that must be a 32 bit unsigned integer.
|
|
173
|
-
// This can happen if the tokenExpirationTime is more than 24.8 days in the future.
|
|
174
|
-
Math.pow(2, 31) - 1
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
if (msBeforeExpiration < 0) {
|
|
178
|
-
return 0;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return msBeforeExpiration;
|
|
182
|
-
}
|
package/src/mock/oidc.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type ParamsOfCreateMockOidc<
|
|
|
29
29
|
const URL_SEARCH_PARAM_NAME = "isUserLoggedIn";
|
|
30
30
|
|
|
31
31
|
export async function createMockOidc<
|
|
32
|
-
DecodedIdToken extends Record<string, unknown> =
|
|
32
|
+
DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
|
|
33
33
|
AutoLogin extends boolean = false
|
|
34
34
|
>(
|
|
35
35
|
params: ParamsOfCreateMockOidc<DecodedIdToken, AutoLogin>
|
|
@@ -130,7 +130,7 @@ export async function createMockOidc<
|
|
|
130
130
|
...common,
|
|
131
131
|
isUserLoggedIn: true,
|
|
132
132
|
renewTokens: async () => {},
|
|
133
|
-
|
|
133
|
+
...(() => {
|
|
134
134
|
const tokens_common: Oidc.Tokens.Common<DecodedIdToken> = {
|
|
135
135
|
accessToken: mockedTokens.accessToken ?? "mocked-access-token",
|
|
136
136
|
accessTokenExpirationTime: mockedTokens.accessTokenExpirationTime ?? Infinity,
|
|
@@ -142,7 +142,16 @@ export async function createMockOidc<
|
|
|
142
142
|
"You haven't provided a mocked decodedIdToken",
|
|
143
143
|
"See https://docs.oidc-spa.dev/v/v6/mock"
|
|
144
144
|
].join("\n")
|
|
145
|
-
})
|
|
145
|
+
}),
|
|
146
|
+
decodedIdToken_original:
|
|
147
|
+
mockedTokens.decodedIdToken_original ??
|
|
148
|
+
createObjectThatThrowsIfAccessed<Oidc.Tokens.DecodedIdToken_base>({
|
|
149
|
+
debugMessage: [
|
|
150
|
+
"You haven't provided a mocked decodedIdToken_original",
|
|
151
|
+
"See https://docs.oidc-spa.dev/v/v6/mock"
|
|
152
|
+
].join("\n")
|
|
153
|
+
}),
|
|
154
|
+
issuedAtTime: Date.now()
|
|
146
155
|
};
|
|
147
156
|
|
|
148
157
|
const tokens: Oidc.Tokens<DecodedIdToken> =
|
|
@@ -158,10 +167,11 @@ export async function createMockOidc<
|
|
|
158
167
|
hasRefreshToken: false
|
|
159
168
|
});
|
|
160
169
|
|
|
161
|
-
return
|
|
170
|
+
return {
|
|
171
|
+
getTokens: () => Promise.resolve(tokens),
|
|
172
|
+
getDecodedIdToken: () => tokens_common.decodedIdToken
|
|
173
|
+
};
|
|
162
174
|
})(),
|
|
163
|
-
getTokens_next: () => Promise.resolve(oidc.getTokens()),
|
|
164
|
-
getDecodedIdToken: () => oidc.getTokens().decodedIdToken,
|
|
165
175
|
subscribeToTokensChange: () => ({
|
|
166
176
|
unsubscribe: () => {}
|
|
167
177
|
}),
|