oidc-spa 8.1.9 → 8.1.11
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/backend.d.ts +27 -6
- package/backend.js +124 -139
- package/backend.js.map +1 -1
- package/core/Oidc.d.ts +28 -4
- package/core/createOidc.d.ts +12 -3
- package/core/createOidc.js +1 -1
- package/core/createOidc.js.map +1 -1
- package/core/earlyInit.d.ts +1 -0
- package/core/earlyInit.js +11 -4
- package/core/earlyInit.js.map +1 -1
- package/core/iframeMessageProtection.js +16 -18
- package/core/iframeMessageProtection.js.map +1 -1
- package/core/loginOrGoToAuthServer.js +8 -3
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/core/loginSilent.js +4 -0
- package/core/loginSilent.js.map +1 -1
- package/core/oidcClientTsUserToTokens.d.ts +1 -1
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/core/requiredPostHydrationReplaceNavigationUrl.d.ts +6 -0
- package/core/requiredPostHydrationReplaceNavigationUrl.js +12 -0
- package/core/requiredPostHydrationReplaceNavigationUrl.js.map +1 -0
- package/entrypoint.d.ts +1 -0
- package/entrypoint.js +3 -1
- package/entrypoint.js.map +1 -1
- package/esm/angular.d.ts +14 -4
- package/esm/angular.js +155 -10
- package/esm/angular.js.map +1 -1
- package/esm/backend.d.ts +48 -0
- package/esm/backend.js +259 -0
- package/esm/backend.js.map +1 -0
- package/esm/core/Oidc.d.ts +28 -4
- package/esm/core/createOidc.d.ts +12 -3
- package/esm/core/createOidc.js +1 -1
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/earlyInit.d.ts +1 -0
- package/esm/core/earlyInit.js +11 -4
- package/esm/core/earlyInit.js.map +1 -1
- package/esm/core/iframeMessageProtection.js +16 -18
- package/esm/core/iframeMessageProtection.js.map +1 -1
- package/esm/core/loginOrGoToAuthServer.js +8 -3
- package/esm/core/loginOrGoToAuthServer.js.map +1 -1
- package/esm/core/loginSilent.js +4 -0
- package/esm/core/loginSilent.js.map +1 -1
- package/esm/core/oidcClientTsUserToTokens.d.ts +1 -1
- package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.d.ts +6 -0
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.js +8 -0
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.js.map +1 -0
- package/esm/entrypoint.d.ts +1 -0
- package/esm/entrypoint.js +1 -0
- package/esm/entrypoint.js.map +1 -1
- package/esm/mock/oidc.d.ts +1 -1
- package/esm/mock/oidc.js.map +1 -1
- package/esm/react/react.d.ts +1 -1
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +12 -0
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js +95 -0
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js.map +1 -0
- package/esm/tanstack-start/react/apiBuilder.d.ts +27 -0
- package/esm/tanstack-start/react/apiBuilder.js +58 -0
- package/esm/tanstack-start/react/apiBuilder.js.map +1 -0
- package/esm/tanstack-start/react/createOidcSpaApi.d.ts +9 -0
- package/esm/tanstack-start/react/createOidcSpaApi.js +678 -0
- package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -0
- package/esm/tanstack-start/react/index.d.ts +3 -0
- package/esm/tanstack-start/react/index.js +4 -0
- package/esm/tanstack-start/react/index.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.d.ts +4 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.js +8 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.d.ts +4 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.js +76 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.d.ts +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.js +11 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.js.map +1 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.d.ts +2 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.js +3 -0
- package/esm/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.js.map +1 -0
- package/esm/tanstack-start/react/types.d.ts +355 -0
- package/esm/tanstack-start/react/types.js +2 -0
- package/esm/tanstack-start/react/types.js.map +1 -0
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.d.ts +2 -0
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js +25 -0
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js.map +1 -0
- package/esm/tools/GetterOrDirectValue.d.ts +1 -0
- package/esm/tools/GetterOrDirectValue.js +2 -0
- package/esm/tools/GetterOrDirectValue.js.map +1 -0
- package/esm/tools/ZodSchemaLike.d.ts +3 -0
- package/esm/tools/ZodSchemaLike.js +2 -0
- package/esm/tools/ZodSchemaLike.js.map +1 -0
- package/esm/tools/inferIsViteDev.d.ts +1 -0
- package/esm/tools/inferIsViteDev.js +6 -0
- package/esm/tools/inferIsViteDev.js.map +1 -0
- package/esm/tools/infer_import_meta_env_BASE_URL.d.ts +1 -0
- package/esm/tools/infer_import_meta_env_BASE_URL.js +15 -0
- package/esm/tools/infer_import_meta_env_BASE_URL.js.map +1 -0
- package/esm/tools/tsafe/uncapitalize.d.ts +2 -0
- package/esm/tools/tsafe/uncapitalize.js +5 -0
- package/esm/tools/tsafe/uncapitalize.js.map +1 -0
- package/esm/vendor/backend/evt.d.ts +2 -0
- package/esm/vendor/backend/evt.js +3286 -0
- package/esm/vendor/backend/jose.d.ts +1 -0
- package/esm/vendor/backend/jose.js +3546 -0
- package/esm/vendor/backend/tsafe.d.ts +5 -0
- package/esm/vendor/backend/tsafe.js +68 -0
- package/esm/vendor/backend/zod.d.ts +1 -0
- package/esm/vendor/backend/zod.js +4023 -0
- package/esm/vendor/frontend/worker-timers.js +261 -1
- package/mock/oidc.d.ts +1 -1
- package/mock/oidc.js.map +1 -1
- package/package.json +40 -4
- package/react/react.d.ts +1 -1
- package/src/angular.ts +224 -9
- package/src/backend.ts +201 -166
- package/src/core/Oidc.ts +41 -11
- package/src/core/createOidc.ts +12 -3
- package/src/core/earlyInit.ts +19 -4
- package/src/core/iframeMessageProtection.ts +14 -15
- package/src/core/loginOrGoToAuthServer.ts +11 -3
- package/src/core/loginSilent.ts +5 -0
- package/src/core/oidcClientTsUserToTokens.ts +2 -2
- package/src/core/requiredPostHydrationReplaceNavigationUrl.ts +11 -0
- package/src/entrypoint.ts +1 -0
- package/src/mock/oidc.ts +2 -2
- package/src/react/react.tsx +1 -1
- package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +135 -0
- package/src/tanstack-start/react/apiBuilder.ts +151 -0
- package/src/tanstack-start/react/createOidcSpaApi.tsx +1009 -0
- package/src/tanstack-start/react/index.ts +5 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError.ts +8 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/enableUnifiedClientRetryForSsrLoaders.tsx +110 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/entrypoint.ts +13 -0
- package/src/tanstack-start/react/rfcUnifiedClientRetryForSsrLoaders/index.ts +2 -0
- package/src/tanstack-start/react/types.tsx +415 -0
- package/src/tanstack-start/react/withHandlingOidcPostLoginNavigation.tsx +35 -0
- package/src/tools/GetterOrDirectValue.ts +1 -0
- package/src/tools/ZodSchemaLike.ts +3 -0
- package/src/tools/getThisCodebaseRootDirPath_cjs.ts +19 -0
- package/src/tools/inferIsViteDev.ts +6 -0
- package/src/tools/infer_import_meta_env_BASE_URL.ts +19 -0
- package/src/tools/tsafe/uncapitalize.ts +4 -0
- package/src/vendor/backend/jose.ts +1 -0
- package/src/vendor/build-runtime/babel.ts +6 -0
- package/src/vendor/build-runtime/magic-string.ts +3 -0
- package/src/vite-plugin/detectProjectType.ts +20 -0
- package/src/vite-plugin/excludeModuleExportFromOptimizedDeps.ts +20 -0
- package/src/vite-plugin/handleClientEntrypoint.ts +260 -0
- package/src/vite-plugin/index.ts +1 -0
- package/src/vite-plugin/transformCreateFileRoute.ts +240 -0
- package/src/vite-plugin/vite-plugin.ts +54 -0
- package/tools/GetterOrDirectValue.d.ts +1 -0
- package/tools/GetterOrDirectValue.js +3 -0
- package/tools/GetterOrDirectValue.js.map +1 -0
- package/tools/ZodSchemaLike.d.ts +3 -0
- package/tools/ZodSchemaLike.js +3 -0
- package/tools/ZodSchemaLike.js.map +1 -0
- package/tools/getThisCodebaseRootDirPath_cjs.d.ts +2 -0
- package/tools/getThisCodebaseRootDirPath_cjs.js +53 -0
- package/tools/getThisCodebaseRootDirPath_cjs.js.map +1 -0
- package/tools/tsafe/uncapitalize.d.ts +2 -0
- package/tools/tsafe/uncapitalize.js +8 -0
- package/tools/tsafe/uncapitalize.js.map +1 -0
- package/vendor/backend/jose.d.ts +1 -0
- package/vendor/backend/jose.js +3 -0
- package/vendor/build-runtime/babel.d.ts +6 -0
- package/vendor/build-runtime/babel.js +3 -0
- package/vendor/build-runtime/magic-string.d.ts +2 -0
- package/vendor/build-runtime/magic-string.js +2 -0
- package/vendor/frontend/oidc-client-ts.js +0 -2
- package/vite-plugin/detectProjectType.d.ts +10 -0
- package/vite-plugin/detectProjectType.js +15 -0
- package/vite-plugin/detectProjectType.js.map +1 -0
- package/vite-plugin/excludeModuleExportFromOptimizedDeps.d.ts +4 -0
- package/vite-plugin/excludeModuleExportFromOptimizedDeps.js +50 -0
- package/vite-plugin/excludeModuleExportFromOptimizedDeps.js.map +1 -0
- package/vite-plugin/handleClientEntrypoint.d.ts +10 -0
- package/vite-plugin/handleClientEntrypoint.js +211 -0
- package/vite-plugin/handleClientEntrypoint.js.map +1 -0
- package/vite-plugin/index.d.ts +1 -0
- package/vite-plugin/index.js +6 -0
- package/vite-plugin/index.js.map +1 -0
- package/vite-plugin/transformCreateFileRoute.d.ts +10 -0
- package/vite-plugin/transformCreateFileRoute.js +173 -0
- package/vite-plugin/transformCreateFileRoute.js.map +1 -0
- package/vite-plugin/vite-plugin.d.ts +5 -0
- package/vite-plugin/vite-plugin.js +46 -0
- package/vite-plugin/vite-plugin.js.map +1 -0
- package/src/vendor/backend/jsonwebtoken.ts +0 -1
- package/src/vendor/backend/node-fetch.ts +0 -2
- package/src/vendor/backend/node-jose.ts +0 -1
- package/vendor/backend/jsonwebtoken.d.ts +0 -1
- package/vendor/backend/jsonwebtoken.js +0 -3
- package/vendor/backend/node-fetch.d.ts +0 -2
- package/vendor/backend/node-fetch.js +0 -2
- package/vendor/backend/node-jose.d.ts +0 -1
- package/vendor/backend/node-jose.js +0 -3
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
import { useState, useEffect, type ReactNode, createContext, useContext } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
CreateValidateAndGetAccessTokenClaims,
|
|
4
|
+
OidcSpaApi,
|
|
5
|
+
CreateOidcComponent,
|
|
6
|
+
GetOidc,
|
|
7
|
+
ParamsOfBootstrap,
|
|
8
|
+
OidcServerContext
|
|
9
|
+
} from "./types";
|
|
10
|
+
import type { ZodSchemaLike } from "../../tools/ZodSchemaLike";
|
|
11
|
+
import type { Oidc as Oidc_core } from "../../core";
|
|
12
|
+
import { OidcInitializationError } from "../../core/OidcInitializationError";
|
|
13
|
+
import { Deferred } from "../../tools/Deferred";
|
|
14
|
+
import { isBrowser } from "../../tools/isBrowser";
|
|
15
|
+
import { assert, type Equals, is } from "../../tools/tsafe/assert";
|
|
16
|
+
import { infer_import_meta_env_BASE_URL } from "../../tools/infer_import_meta_env_BASE_URL";
|
|
17
|
+
import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
|
|
18
|
+
import { createStatefulEvt } from "../../tools/StatefulEvt";
|
|
19
|
+
import { id } from "../../tools/tsafe/id";
|
|
20
|
+
import { typeGuard } from "../../tools/tsafe/typeGuard";
|
|
21
|
+
import type { GetterOrDirectValue } from "../../tools/GetterOrDirectValue";
|
|
22
|
+
import { createServerFn, createMiddleware } from "@tanstack/react-start";
|
|
23
|
+
// @ts-expect-error: Since our module is not labeled as ESM we don't have the types here.
|
|
24
|
+
import { getRequest, setResponseHeader, setResponseStatus } from "@tanstack/react-start/server";
|
|
25
|
+
import { toFullyQualifiedUrl } from "../../tools/toFullyQualifiedUrl";
|
|
26
|
+
import { UnifiedClientRetryForSsrLoadersError } from "./rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError";
|
|
27
|
+
|
|
28
|
+
export function createOidcSpaApi<
|
|
29
|
+
AutoLogin extends boolean,
|
|
30
|
+
DecodedIdToken extends Record<string, unknown>,
|
|
31
|
+
AccessTokenClaims extends Record<string, unknown> | undefined
|
|
32
|
+
>(params: {
|
|
33
|
+
autoLogin: AutoLogin;
|
|
34
|
+
decodedIdTokenSchema:
|
|
35
|
+
| ZodSchemaLike<Oidc_core.Tokens.DecodedIdToken_OidcCoreSpec, DecodedIdToken>
|
|
36
|
+
| undefined;
|
|
37
|
+
decodedIdToken_mock: DecodedIdToken | undefined;
|
|
38
|
+
createValidateAndGetAccessTokenClaims:
|
|
39
|
+
| CreateValidateAndGetAccessTokenClaims<AccessTokenClaims>
|
|
40
|
+
| undefined;
|
|
41
|
+
}): OidcSpaApi<AutoLogin, DecodedIdToken, AccessTokenClaims> {
|
|
42
|
+
const {
|
|
43
|
+
autoLogin,
|
|
44
|
+
decodedIdTokenSchema,
|
|
45
|
+
decodedIdToken_mock,
|
|
46
|
+
createValidateAndGetAccessTokenClaims
|
|
47
|
+
} = params;
|
|
48
|
+
|
|
49
|
+
const dParamsOfBootstrap = new Deferred<
|
|
50
|
+
ParamsOfBootstrap<AutoLogin, DecodedIdToken, AccessTokenClaims>
|
|
51
|
+
>();
|
|
52
|
+
|
|
53
|
+
const dOidcCoreOrInitializationError = new Deferred<
|
|
54
|
+
Oidc_core<DecodedIdToken> | OidcInitializationError
|
|
55
|
+
>();
|
|
56
|
+
|
|
57
|
+
const evtAutoLogoutState = createStatefulEvt<
|
|
58
|
+
CreateOidcComponent.Oidc.LoggedIn<unknown>["autoLogoutState"]
|
|
59
|
+
>(() => ({
|
|
60
|
+
shouldDisplayWarning: false
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
dOidcCoreOrInitializationError.pr.then(oidcCoreOrInitializationError => {
|
|
64
|
+
const { hasResolved, value: paramsOfBootstrap } = dParamsOfBootstrap.getState();
|
|
65
|
+
|
|
66
|
+
assert(hasResolved);
|
|
67
|
+
|
|
68
|
+
if (paramsOfBootstrap.implementation === "mock") {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
assert<Equals<typeof paramsOfBootstrap.implementation, "real">>;
|
|
72
|
+
|
|
73
|
+
const { startCountdownSecondsBeforeAutoLogout = 45 } = paramsOfBootstrap;
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
oidcCoreOrInitializationError === undefined ||
|
|
77
|
+
oidcCoreOrInitializationError instanceof OidcInitializationError
|
|
78
|
+
) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const oidcCore = oidcCoreOrInitializationError;
|
|
83
|
+
|
|
84
|
+
if (!oidcCore.isUserLoggedIn) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
oidcCore.subscribeToAutoLogoutCountdown(({ secondsLeft }) => {
|
|
89
|
+
const newState: CreateOidcComponent.Oidc.LoggedIn<unknown>["autoLogoutState"] = (() => {
|
|
90
|
+
if (secondsLeft === undefined) {
|
|
91
|
+
return {
|
|
92
|
+
shouldDisplayWarning: false
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (secondsLeft > startCountdownSecondsBeforeAutoLogout) {
|
|
97
|
+
return {
|
|
98
|
+
shouldDisplayWarning: false
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
shouldDisplayWarning: true,
|
|
104
|
+
secondsLeftBeforeAutoLogout: secondsLeft
|
|
105
|
+
};
|
|
106
|
+
})();
|
|
107
|
+
|
|
108
|
+
if (!newState.shouldDisplayWarning && !evtAutoLogoutState.current.shouldDisplayWarning) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
evtAutoLogoutState.current = newState;
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function useOidc(): CreateOidcComponent.Oidc<DecodedIdToken> {
|
|
117
|
+
const { hasResolved, value: oidcCore } = dOidcCoreOrInitializationError.getState();
|
|
118
|
+
|
|
119
|
+
assert(hasResolved);
|
|
120
|
+
assert(!(oidcCore instanceof OidcInitializationError));
|
|
121
|
+
|
|
122
|
+
const [, reRenderIfDecodedIdTokenChanged] = useState<DecodedIdToken | undefined>(() => {
|
|
123
|
+
if (!oidcCore.isUserLoggedIn) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return oidcCore.getDecodedIdToken();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const [evtIsDecodedIdTokenUsed] = useState(() => createStatefulEvt<boolean>(() => false));
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!oidcCore.isUserLoggedIn) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let isActive = true;
|
|
137
|
+
|
|
138
|
+
let unsubscribe: (() => void) | undefined = undefined;
|
|
139
|
+
|
|
140
|
+
(async () => {
|
|
141
|
+
if (!evtIsDecodedIdTokenUsed.current) {
|
|
142
|
+
const dDecodedIdTokenUsed = new Deferred<void>();
|
|
143
|
+
|
|
144
|
+
const { unsubscribe: unsubscribe_scope } = evtIsDecodedIdTokenUsed.subscribe(() => {
|
|
145
|
+
unsubscribe_scope();
|
|
146
|
+
dDecodedIdTokenUsed.resolve();
|
|
147
|
+
});
|
|
148
|
+
unsubscribe = unsubscribe_scope;
|
|
149
|
+
|
|
150
|
+
await dDecodedIdTokenUsed.pr;
|
|
151
|
+
|
|
152
|
+
if (!isActive) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
reRenderIfDecodedIdTokenChanged(oidcCore.getDecodedIdToken());
|
|
158
|
+
|
|
159
|
+
unsubscribe = oidcCore.subscribeToTokensChange(() => {
|
|
160
|
+
reRenderIfDecodedIdTokenChanged(oidcCore.getDecodedIdToken());
|
|
161
|
+
}).unsubscribe;
|
|
162
|
+
})();
|
|
163
|
+
|
|
164
|
+
return () => {
|
|
165
|
+
isActive = false;
|
|
166
|
+
unsubscribe?.();
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
const [evtIsAutoLogoutStateUsed] = useState(() => createStatefulEvt<boolean>(() => false));
|
|
171
|
+
|
|
172
|
+
const [, reRenderIfAutoLogoutStateChanged] = useState(() => evtAutoLogoutState.current);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
let isActive = true;
|
|
176
|
+
let unsubscribe: (() => void) | undefined = undefined;
|
|
177
|
+
|
|
178
|
+
(async () => {
|
|
179
|
+
if (!evtIsAutoLogoutStateUsed.current) {
|
|
180
|
+
const dAutoLogoutStateUsed = new Deferred<void>();
|
|
181
|
+
|
|
182
|
+
const { unsubscribe: unsubscribe_scope } = evtIsAutoLogoutStateUsed.subscribe(() => {
|
|
183
|
+
unsubscribe_scope();
|
|
184
|
+
dAutoLogoutStateUsed.resolve();
|
|
185
|
+
});
|
|
186
|
+
unsubscribe = unsubscribe_scope;
|
|
187
|
+
|
|
188
|
+
await dAutoLogoutStateUsed.pr;
|
|
189
|
+
|
|
190
|
+
if (!isActive) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
reRenderIfAutoLogoutStateChanged(evtAutoLogoutState.current);
|
|
196
|
+
|
|
197
|
+
unsubscribe = evtAutoLogoutState.subscribe(reRenderIfAutoLogoutStateChanged).unsubscribe;
|
|
198
|
+
})();
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
isActive = false;
|
|
202
|
+
unsubscribe?.();
|
|
203
|
+
};
|
|
204
|
+
}, []);
|
|
205
|
+
|
|
206
|
+
if (!oidcCore.isUserLoggedIn) {
|
|
207
|
+
return id<CreateOidcComponent.Oidc.NotLoggedIn>({
|
|
208
|
+
isUserLoggedIn: false,
|
|
209
|
+
initializationError: oidcCore.initializationError,
|
|
210
|
+
issuerUri: oidcCore.params.issuerUri,
|
|
211
|
+
clientId: oidcCore.params.clientId,
|
|
212
|
+
autoLogoutState: { shouldDisplayWarning: false },
|
|
213
|
+
login: params =>
|
|
214
|
+
oidcCore.login({
|
|
215
|
+
doesCurrentHrefRequiresAuth: false,
|
|
216
|
+
...params
|
|
217
|
+
})
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return id<CreateOidcComponent.Oidc.LoggedIn<DecodedIdToken>>({
|
|
222
|
+
isUserLoggedIn: true,
|
|
223
|
+
get decodedIdToken() {
|
|
224
|
+
evtIsDecodedIdTokenUsed.current = true;
|
|
225
|
+
return oidcCore.getDecodedIdToken();
|
|
226
|
+
},
|
|
227
|
+
logout: oidcCore.logout,
|
|
228
|
+
renewTokens: oidcCore.renewTokens,
|
|
229
|
+
goToAuthServer: oidcCore.goToAuthServer,
|
|
230
|
+
backFromAuthServer: oidcCore.backFromAuthServer,
|
|
231
|
+
isNewBrowserSession: oidcCore.isNewBrowserSession,
|
|
232
|
+
get autoLogoutState() {
|
|
233
|
+
evtIsAutoLogoutStateUsed.current = true;
|
|
234
|
+
return evtAutoLogoutState.current;
|
|
235
|
+
},
|
|
236
|
+
issuerUri: oidcCore.params.issuerUri,
|
|
237
|
+
clientId: oidcCore.params.clientId
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const context_isFreeOfSsrHydrationConcern = createContext<boolean>(false);
|
|
242
|
+
|
|
243
|
+
function createOidcComponent<Props extends Record<string, unknown>>(params: {
|
|
244
|
+
assert?: "user logged in" | "user not logged in";
|
|
245
|
+
pendingComponent?: (props: NoInfer<Props>) => ReactNode;
|
|
246
|
+
component: (props: Props) => ReactNode;
|
|
247
|
+
}): ((props: Props) => ReactNode) & {
|
|
248
|
+
useOidc: () => CreateOidcComponent.Oidc<DecodedIdToken>;
|
|
249
|
+
} {
|
|
250
|
+
const {
|
|
251
|
+
assert: assert_params,
|
|
252
|
+
pendingComponent: PendingComponent,
|
|
253
|
+
component: Component
|
|
254
|
+
} = params;
|
|
255
|
+
|
|
256
|
+
const checkAssertion =
|
|
257
|
+
assert_params === undefined
|
|
258
|
+
? undefined
|
|
259
|
+
: (params: { isUserLoggedIn: boolean }): void => {
|
|
260
|
+
const { isUserLoggedIn } = params;
|
|
261
|
+
|
|
262
|
+
switch (assert_params) {
|
|
263
|
+
case "user not logged in":
|
|
264
|
+
if (isUserLoggedIn) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
[
|
|
267
|
+
"oidc-spa: Asserted the user should not be logged in",
|
|
268
|
+
"but they are. Check your control flow."
|
|
269
|
+
].join(" ")
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
case "user logged in":
|
|
274
|
+
if (!isUserLoggedIn) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
[
|
|
277
|
+
"oidc-spa: Asserted the user should be logged in",
|
|
278
|
+
"but they arn't. Check your control flow."
|
|
279
|
+
].join(" ")
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
default:
|
|
284
|
+
assert<Equals<typeof assert_params, never>>;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
function ComponentWithOidc(props: Props) {
|
|
289
|
+
const renderFallback = () =>
|
|
290
|
+
PendingComponent === undefined ? null : <PendingComponent {...props} />;
|
|
291
|
+
|
|
292
|
+
if (!isBrowser) {
|
|
293
|
+
return renderFallback();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// NOTE: When the user assert that the user is logged in or not, they know.
|
|
297
|
+
// if they knows it means that they learned it somewhere so we are post SSR.
|
|
298
|
+
// Additionally, in autoLogin mode, the typedef don't allow this param to be provided.
|
|
299
|
+
const isFreeOfSsrHydrationConcern =
|
|
300
|
+
useContext(context_isFreeOfSsrHydrationConcern) || assert_params !== undefined;
|
|
301
|
+
|
|
302
|
+
const [oidcCore, setOidcCore] = useState<Oidc_core<DecodedIdToken> | undefined>(() => {
|
|
303
|
+
if (!isFreeOfSsrHydrationConcern) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const { hasResolved, value: oidcCore } = dOidcCoreOrInitializationError.getState();
|
|
308
|
+
|
|
309
|
+
if (!hasResolved) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (oidcCore instanceof OidcInitializationError) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
checkAssertion?.({
|
|
318
|
+
isUserLoggedIn: oidcCore.isUserLoggedIn
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return oidcCore;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
if (oidcCore !== undefined) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let isActive = true;
|
|
330
|
+
|
|
331
|
+
dOidcCoreOrInitializationError.pr.then(oidcCore => {
|
|
332
|
+
if (!isActive) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (oidcCore instanceof OidcInitializationError) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
checkAssertion?.({
|
|
341
|
+
isUserLoggedIn: oidcCore.isUserLoggedIn
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
setOidcCore(oidcCore);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return () => {
|
|
348
|
+
isActive = false;
|
|
349
|
+
};
|
|
350
|
+
}, []);
|
|
351
|
+
|
|
352
|
+
if (oidcCore === undefined) {
|
|
353
|
+
return PendingComponent === undefined ? null : <PendingComponent {...props} />;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<context_isFreeOfSsrHydrationConcern.Provider value={true}>
|
|
358
|
+
<Component {...props} />
|
|
359
|
+
</context_isFreeOfSsrHydrationConcern.Provider>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
ComponentWithOidc.displayName = `${
|
|
364
|
+
(Component as any).displayName ?? Component.name ?? "Component"
|
|
365
|
+
}WithOidc`;
|
|
366
|
+
|
|
367
|
+
ComponentWithOidc.useOidc = useOidc;
|
|
368
|
+
|
|
369
|
+
return ComponentWithOidc;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function getOidc(params?: {
|
|
373
|
+
assert?: "user logged in" | "user not logged in";
|
|
374
|
+
}): Promise<GetOidc.Oidc<DecodedIdToken>> {
|
|
375
|
+
if (!isBrowser) {
|
|
376
|
+
throw new UnifiedClientRetryForSsrLoadersError(
|
|
377
|
+
[
|
|
378
|
+
"oidc-spa: getOidc() can't be used on the server",
|
|
379
|
+
"if you use it in a loader, make sure to mark the route",
|
|
380
|
+
"as `ssr: false`."
|
|
381
|
+
].join(" ")
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const oidcCore = await dOidcCoreOrInitializationError.pr;
|
|
386
|
+
|
|
387
|
+
if (oidcCore instanceof OidcInitializationError) {
|
|
388
|
+
return new Promise<never>(() => {});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (params?.assert === "user logged in" && !oidcCore.isUserLoggedIn) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
[
|
|
394
|
+
"oidc-spa: Called getOidc({ assert: 'user logged in' })",
|
|
395
|
+
"but the user is not currently logged in."
|
|
396
|
+
].join(" ")
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
if (params?.assert === "user not logged in" && oidcCore.isUserLoggedIn) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
[
|
|
402
|
+
"oidc-spa: Called getOidc({ assert: 'user not logged in' })",
|
|
403
|
+
"but the user is currently logged in."
|
|
404
|
+
].join(" ")
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return oidcCore.isUserLoggedIn
|
|
409
|
+
? id<GetOidc.Oidc.LoggedIn<DecodedIdToken>>({
|
|
410
|
+
issuerUri: oidcCore.params.issuerUri,
|
|
411
|
+
clientId: oidcCore.params.clientId,
|
|
412
|
+
isUserLoggedIn: true,
|
|
413
|
+
getAccessToken: async () => {
|
|
414
|
+
const { accessToken } = await oidcCore.getTokens();
|
|
415
|
+
return accessToken;
|
|
416
|
+
},
|
|
417
|
+
getDecodedIdToken: oidcCore.getDecodedIdToken,
|
|
418
|
+
logout: oidcCore.logout,
|
|
419
|
+
renewTokens: oidcCore.renewTokens,
|
|
420
|
+
goToAuthServer: oidcCore.goToAuthServer,
|
|
421
|
+
backFromAuthServer: oidcCore.backFromAuthServer,
|
|
422
|
+
isNewBrowserSession: oidcCore.isNewBrowserSession,
|
|
423
|
+
subscribeToAutoLogoutState: next => {
|
|
424
|
+
next(evtAutoLogoutState.current);
|
|
425
|
+
|
|
426
|
+
const { unsubscribe } = evtAutoLogoutState.subscribe(next);
|
|
427
|
+
|
|
428
|
+
return { unsubscribeFromAutoLogoutState: unsubscribe };
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
: id<GetOidc.Oidc.NotLoggedIn>({
|
|
432
|
+
issuerUri: oidcCore.params.issuerUri,
|
|
433
|
+
clientId: oidcCore.params.clientId,
|
|
434
|
+
isUserLoggedIn: false,
|
|
435
|
+
initializationError: oidcCore.initializationError,
|
|
436
|
+
login: oidcCore.login
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let hasBootstrapBeenCalled = false;
|
|
441
|
+
|
|
442
|
+
const prModuleCore = !isBrowser ? undefined : import("../../core");
|
|
443
|
+
|
|
444
|
+
const bootstrapOidc = (
|
|
445
|
+
getParamsOfBootstrapOrDirectValue: GetterOrDirectValue<
|
|
446
|
+
{ process: { env: Record<string, string> } },
|
|
447
|
+
ParamsOfBootstrap<AutoLogin, DecodedIdToken, AccessTokenClaims>
|
|
448
|
+
>
|
|
449
|
+
) => {
|
|
450
|
+
if (hasBootstrapBeenCalled) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
hasBootstrapBeenCalled = true;
|
|
455
|
+
|
|
456
|
+
(async () => {
|
|
457
|
+
const getParamsOfBootstrap =
|
|
458
|
+
typeof getParamsOfBootstrapOrDirectValue === "function"
|
|
459
|
+
? getParamsOfBootstrapOrDirectValue
|
|
460
|
+
: () => getParamsOfBootstrapOrDirectValue;
|
|
461
|
+
|
|
462
|
+
if (!isBrowser) {
|
|
463
|
+
const missingEnvNames = new Set<string>();
|
|
464
|
+
|
|
465
|
+
const env_proxy = new Proxy<Record<string, string>>(
|
|
466
|
+
{},
|
|
467
|
+
{
|
|
468
|
+
get: (...[, envName]) => {
|
|
469
|
+
assert(typeof envName === "string");
|
|
470
|
+
|
|
471
|
+
const value = process.env[envName];
|
|
472
|
+
|
|
473
|
+
if (value === undefined) {
|
|
474
|
+
missingEnvNames.add(envName);
|
|
475
|
+
return "";
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return value;
|
|
479
|
+
},
|
|
480
|
+
has: (...[, envName]) => {
|
|
481
|
+
assert(typeof envName === "string");
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
const paramsOfBootstrap = getParamsOfBootstrap({ process: { env: env_proxy } });
|
|
488
|
+
|
|
489
|
+
if (
|
|
490
|
+
paramsOfBootstrap.implementation === "real" &&
|
|
491
|
+
(!paramsOfBootstrap.issuerUri || !paramsOfBootstrap.clientId)
|
|
492
|
+
) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
[
|
|
495
|
+
"oidc-spa: Incorrect configuration provided:\n",
|
|
496
|
+
JSON.stringify(paramsOfBootstrap, null, 2),
|
|
497
|
+
...(missingEnvNames.size === 0
|
|
498
|
+
? []
|
|
499
|
+
: [
|
|
500
|
+
"\nYou probably forgot to define the environnement variables:",
|
|
501
|
+
Array.from(missingEnvNames).join(", ")
|
|
502
|
+
])
|
|
503
|
+
].join(" ")
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
dParamsOfBootstrap.resolve(paramsOfBootstrap);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
assert(prModuleCore !== undefined);
|
|
512
|
+
|
|
513
|
+
const paramsOfBootstrap = await (async () => {
|
|
514
|
+
let envNamesToPullFromServer = new Set<string>();
|
|
515
|
+
|
|
516
|
+
const env: Record<string, string> = {};
|
|
517
|
+
|
|
518
|
+
const env_proxy = new Proxy(env, {
|
|
519
|
+
get: (...[, envName]) => {
|
|
520
|
+
assert(typeof envName === "string");
|
|
521
|
+
|
|
522
|
+
if (envName in env) {
|
|
523
|
+
return env[envName];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
envNamesToPullFromServer.add(envName);
|
|
527
|
+
|
|
528
|
+
return "oidc_spa_probe";
|
|
529
|
+
},
|
|
530
|
+
has: (...[, envName]) => {
|
|
531
|
+
assert(typeof envName === "string");
|
|
532
|
+
|
|
533
|
+
if (envName in env) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
envNamesToPullFromServer.add(envName);
|
|
538
|
+
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
let result:
|
|
544
|
+
| {
|
|
545
|
+
hasThrown: false;
|
|
546
|
+
paramsOfBootstrap: ParamsOfBootstrap<
|
|
547
|
+
AutoLogin,
|
|
548
|
+
DecodedIdToken,
|
|
549
|
+
AccessTokenClaims
|
|
550
|
+
>;
|
|
551
|
+
}
|
|
552
|
+
| {
|
|
553
|
+
hasThrown: true;
|
|
554
|
+
error: unknown;
|
|
555
|
+
}
|
|
556
|
+
| undefined = undefined;
|
|
557
|
+
|
|
558
|
+
while (true) {
|
|
559
|
+
envNamesToPullFromServer = new Set();
|
|
560
|
+
|
|
561
|
+
result = undefined;
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
const paramsOfBootstrap = getParamsOfBootstrap({ process: { env: env_proxy } });
|
|
565
|
+
result = {
|
|
566
|
+
hasThrown: false,
|
|
567
|
+
paramsOfBootstrap
|
|
568
|
+
};
|
|
569
|
+
} catch (error) {
|
|
570
|
+
result = {
|
|
571
|
+
hasThrown: true,
|
|
572
|
+
error
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (envNamesToPullFromServer.size === 0) {
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
Object.entries(
|
|
581
|
+
await fetchServerEnvVariableValues({
|
|
582
|
+
data: {
|
|
583
|
+
envVarNames: Array.from(envNamesToPullFromServer)
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
).forEach(([envName, value]) => {
|
|
587
|
+
env[envName] = value;
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (result.hasThrown) {
|
|
592
|
+
throw new Error(
|
|
593
|
+
[
|
|
594
|
+
"oidc-spa: The function argument passed to bootstrapOidc",
|
|
595
|
+
"has thrown when invoked."
|
|
596
|
+
].join(" "),
|
|
597
|
+
//@ts-expect-error
|
|
598
|
+
{ cause: result.error }
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return result.paramsOfBootstrap;
|
|
603
|
+
})();
|
|
604
|
+
|
|
605
|
+
dParamsOfBootstrap.resolve(paramsOfBootstrap);
|
|
606
|
+
|
|
607
|
+
switch (paramsOfBootstrap.implementation) {
|
|
608
|
+
case "mock":
|
|
609
|
+
{
|
|
610
|
+
const { createMockOidc } = await import("../../mock/oidc");
|
|
611
|
+
|
|
612
|
+
const oidcCore = await createMockOidc({
|
|
613
|
+
homeUrl: infer_import_meta_env_BASE_URL(),
|
|
614
|
+
// NOTE: The `as false` is lying here, it's just to preserve some level of type-safety.
|
|
615
|
+
autoLogin: autoLogin as false,
|
|
616
|
+
// NOTE: Same here, the nullish coalescing is lying.
|
|
617
|
+
isUserInitiallyLoggedIn: paramsOfBootstrap.isUserInitiallyLoggedIn!,
|
|
618
|
+
mockedParams: {
|
|
619
|
+
clientId: paramsOfBootstrap.clientId_mock,
|
|
620
|
+
issuerUri: paramsOfBootstrap.issuerUri_mock
|
|
621
|
+
},
|
|
622
|
+
mockedTokens: {
|
|
623
|
+
decodedIdToken:
|
|
624
|
+
paramsOfBootstrap.decodedIdToken_mock ??
|
|
625
|
+
decodedIdToken_mock ??
|
|
626
|
+
createObjectThatThrowsIfAccessed<DecodedIdToken>({
|
|
627
|
+
debugMessage: [
|
|
628
|
+
"oidc-spa: You didn't provide any mock for the decodedIdToken",
|
|
629
|
+
"Either provide a default one by specifying decodedIdToken_mock",
|
|
630
|
+
"as parameter of .withExpectedDecodedIdTokenShape() or",
|
|
631
|
+
"specify decodedIdToken_mock when calling bootstrapOidc()"
|
|
632
|
+
].join(" ")
|
|
633
|
+
})
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
dOidcCoreOrInitializationError.resolve(oidcCore);
|
|
638
|
+
}
|
|
639
|
+
break;
|
|
640
|
+
case "real":
|
|
641
|
+
{
|
|
642
|
+
const { createOidc } = await prModuleCore;
|
|
643
|
+
|
|
644
|
+
const homeUrl = infer_import_meta_env_BASE_URL();
|
|
645
|
+
|
|
646
|
+
let oidcCoreOrInitializationError:
|
|
647
|
+
| Oidc_core<DecodedIdToken>
|
|
648
|
+
| OidcInitializationError;
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
oidcCoreOrInitializationError = await createOidc({
|
|
652
|
+
homeUrl,
|
|
653
|
+
autoLogin,
|
|
654
|
+
decodedIdTokenSchema,
|
|
655
|
+
issuerUri: paramsOfBootstrap.issuerUri,
|
|
656
|
+
clientId: paramsOfBootstrap.clientId,
|
|
657
|
+
idleSessionLifetimeInSeconds:
|
|
658
|
+
paramsOfBootstrap.idleSessionLifetimeInSeconds,
|
|
659
|
+
scopes: paramsOfBootstrap.scopes,
|
|
660
|
+
transformUrlBeforeRedirect: paramsOfBootstrap.transformUrlBeforeRedirect,
|
|
661
|
+
extraQueryParams: paramsOfBootstrap.extraQueryParams,
|
|
662
|
+
extraTokenParams: paramsOfBootstrap.extraTokenParams,
|
|
663
|
+
noIframe: paramsOfBootstrap.noIframe,
|
|
664
|
+
debugLogs: paramsOfBootstrap.debugLogs,
|
|
665
|
+
__unsafe_clientSecret: paramsOfBootstrap.__unsafe_clientSecret,
|
|
666
|
+
__metadata: paramsOfBootstrap.__metadata
|
|
667
|
+
});
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (!(error instanceof OidcInitializationError)) {
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
dOidcCoreOrInitializationError.resolve(error);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
dOidcCoreOrInitializationError.resolve(oidcCoreOrInitializationError);
|
|
677
|
+
}
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
})();
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
async function enforceLogin(loaderContext: {
|
|
684
|
+
cause: "preload" | string;
|
|
685
|
+
location: {
|
|
686
|
+
href: string;
|
|
687
|
+
};
|
|
688
|
+
}): Promise<void | never> {
|
|
689
|
+
if (!isBrowser) {
|
|
690
|
+
throw new UnifiedClientRetryForSsrLoadersError(
|
|
691
|
+
[
|
|
692
|
+
"oidc-spa: enforceLogin cannot be used on the server",
|
|
693
|
+
"make sure to mark any route that uses it as ssr: false"
|
|
694
|
+
].join(" ")
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const { cause } = loaderContext;
|
|
699
|
+
|
|
700
|
+
const redirectUrl = (() => {
|
|
701
|
+
if (loaderContext.location?.href !== undefined) {
|
|
702
|
+
return toFullyQualifiedUrl({
|
|
703
|
+
urlish: loaderContext.location.href,
|
|
704
|
+
doAssertNoQueryParams: false
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return location.href;
|
|
709
|
+
})();
|
|
710
|
+
|
|
711
|
+
const oidc = await getOidc();
|
|
712
|
+
|
|
713
|
+
if (!oidc.isUserLoggedIn) {
|
|
714
|
+
if (cause === "preload") {
|
|
715
|
+
throw new Error(
|
|
716
|
+
[
|
|
717
|
+
"oidc-spa: User is not yet logged in.",
|
|
718
|
+
"This is not an error, this is an expected case.",
|
|
719
|
+
"It's only TanStack Router using exception as control flow."
|
|
720
|
+
].join(" ")
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const doesCurrentHrefRequiresAuth =
|
|
724
|
+
location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
725
|
+
|
|
726
|
+
await oidc.login({
|
|
727
|
+
redirectUrl,
|
|
728
|
+
doesCurrentHrefRequiresAuth
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function OidcInitializationGate(props: {
|
|
734
|
+
renderFallback: (props: {
|
|
735
|
+
initializationError: OidcInitializationError | undefined;
|
|
736
|
+
}) => ReactNode;
|
|
737
|
+
children: ReactNode;
|
|
738
|
+
}): ReactNode {
|
|
739
|
+
const { renderFallback, children } = props;
|
|
740
|
+
|
|
741
|
+
const [oidcCoreOrInitializationError, setOidcCoreOrInitializationError] = useState<
|
|
742
|
+
Oidc_core<DecodedIdToken> | OidcInitializationError | undefined
|
|
743
|
+
>(undefined);
|
|
744
|
+
|
|
745
|
+
useEffect(() => {
|
|
746
|
+
let isActive = true;
|
|
747
|
+
|
|
748
|
+
dOidcCoreOrInitializationError.pr.then(oidcCoreOrInitializationError => {
|
|
749
|
+
if (!isActive) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
setOidcCoreOrInitializationError(oidcCoreOrInitializationError);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
return () => {
|
|
756
|
+
isActive = false;
|
|
757
|
+
};
|
|
758
|
+
}, []);
|
|
759
|
+
|
|
760
|
+
if (
|
|
761
|
+
oidcCoreOrInitializationError === undefined ||
|
|
762
|
+
oidcCoreOrInitializationError instanceof OidcInitializationError
|
|
763
|
+
) {
|
|
764
|
+
return renderFallback({ initializationError: oidcCoreOrInitializationError });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return (
|
|
768
|
+
<context_isFreeOfSsrHydrationConcern.Provider value={true}>
|
|
769
|
+
{children}
|
|
770
|
+
</context_isFreeOfSsrHydrationConcern.Provider>
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const prValidateAndGetAccessTokenClaims =
|
|
775
|
+
createValidateAndGetAccessTokenClaims === undefined
|
|
776
|
+
? undefined
|
|
777
|
+
: dParamsOfBootstrap.pr.then(paramsOfBootstrap =>
|
|
778
|
+
createValidateAndGetAccessTokenClaims({
|
|
779
|
+
// @ts-expect-error
|
|
780
|
+
paramsOfBootstrap
|
|
781
|
+
})
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
function createFunctionMiddlewareServerFn(params?: {
|
|
785
|
+
assert?: "user logged in";
|
|
786
|
+
hasRequiredClaims?: (params: { accessTokenClaims: AccessTokenClaims }) => Promise<boolean>;
|
|
787
|
+
}) {
|
|
788
|
+
return async (options: {
|
|
789
|
+
next: (options: { context: { oidc: OidcServerContext<AccessTokenClaims> } }) => any;
|
|
790
|
+
}): Promise<any> => {
|
|
791
|
+
const { next } = options;
|
|
792
|
+
|
|
793
|
+
const unauthorized = (params: {
|
|
794
|
+
errorMessage: string;
|
|
795
|
+
wwwAuthenticateHeaderErrorDescription: string;
|
|
796
|
+
}) => {
|
|
797
|
+
const { errorMessage, wwwAuthenticateHeaderErrorDescription } = params;
|
|
798
|
+
|
|
799
|
+
setResponseHeader(
|
|
800
|
+
"WWW-Authenticate",
|
|
801
|
+
`Bearer error="invalid_token", error_description="${wwwAuthenticateHeaderErrorDescription}"`
|
|
802
|
+
);
|
|
803
|
+
setResponseStatus(401, "Unauthorized");
|
|
804
|
+
|
|
805
|
+
return new Error(`oidc-spa: ${errorMessage}`);
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const { headers } = getRequest();
|
|
809
|
+
|
|
810
|
+
const authorizationHeaderValue = headers.get("Authorization");
|
|
811
|
+
|
|
812
|
+
if (authorizationHeaderValue === null) {
|
|
813
|
+
if (params?.assert === "user logged in") {
|
|
814
|
+
const errorMessage = [
|
|
815
|
+
"Asserted user logged in for that serverFn request",
|
|
816
|
+
"but no access token was attached to the request"
|
|
817
|
+
].join(" ");
|
|
818
|
+
|
|
819
|
+
throw unauthorized({
|
|
820
|
+
errorMessage,
|
|
821
|
+
wwwAuthenticateHeaderErrorDescription: errorMessage
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return next({
|
|
826
|
+
context: {
|
|
827
|
+
oidc: id<OidcServerContext<AccessTokenClaims>>(
|
|
828
|
+
id<OidcServerContext.NotLoggedIn>({
|
|
829
|
+
isUserLoggedIn: false
|
|
830
|
+
})
|
|
831
|
+
)
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const accessToken = (() => {
|
|
837
|
+
const prefix = "Bearer ";
|
|
838
|
+
|
|
839
|
+
if (!authorizationHeaderValue.startsWith(prefix)) {
|
|
840
|
+
return undefined;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return authorizationHeaderValue.slice(prefix.length);
|
|
844
|
+
})();
|
|
845
|
+
|
|
846
|
+
if (accessToken === undefined) {
|
|
847
|
+
const errorMessage =
|
|
848
|
+
"Missing well formed Authorization header with Bearer <access_token>";
|
|
849
|
+
|
|
850
|
+
throw unauthorized({
|
|
851
|
+
errorMessage,
|
|
852
|
+
wwwAuthenticateHeaderErrorDescription: errorMessage
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
assert(prValidateAndGetAccessTokenClaims !== undefined);
|
|
857
|
+
|
|
858
|
+
const { validateAndGetAccessTokenClaims } = await prValidateAndGetAccessTokenClaims;
|
|
859
|
+
|
|
860
|
+
const resultOfValidate = await validateAndGetAccessTokenClaims({ accessToken });
|
|
861
|
+
|
|
862
|
+
if (!resultOfValidate.isValid) {
|
|
863
|
+
const { errorMessage, wwwAuthenticateHeaderErrorDescription } = resultOfValidate;
|
|
864
|
+
|
|
865
|
+
throw unauthorized({
|
|
866
|
+
errorMessage,
|
|
867
|
+
wwwAuthenticateHeaderErrorDescription
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const { accessTokenClaims } = resultOfValidate;
|
|
872
|
+
|
|
873
|
+
assert(is<Exclude<AccessTokenClaims, undefined>>(accessTokenClaims));
|
|
874
|
+
|
|
875
|
+
check_required_claims: {
|
|
876
|
+
const getHasRequiredClaims = params?.hasRequiredClaims;
|
|
877
|
+
|
|
878
|
+
if (getHasRequiredClaims === undefined) {
|
|
879
|
+
break check_required_claims;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const accessedClaimNames = new Set<string>();
|
|
883
|
+
|
|
884
|
+
const accessTokenClaims_proxy = new Proxy(accessTokenClaims, {
|
|
885
|
+
get(...args) {
|
|
886
|
+
const [, claimName] = args;
|
|
887
|
+
|
|
888
|
+
record_claim_access: {
|
|
889
|
+
if (typeof claimName !== "string") {
|
|
890
|
+
break record_claim_access;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
accessedClaimNames.add(claimName);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return Reflect.get(...args);
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
const hasRequiredClaims = await getHasRequiredClaims({
|
|
901
|
+
accessTokenClaims: accessTokenClaims_proxy
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
if (hasRequiredClaims) {
|
|
905
|
+
break check_required_claims;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const errorMessage = [
|
|
909
|
+
"Missing or invalid required access token claim.",
|
|
910
|
+
`Related to claims: ${Array.from(accessedClaimNames).join(" and/or ")}`
|
|
911
|
+
].join(" ");
|
|
912
|
+
|
|
913
|
+
throw unauthorized({
|
|
914
|
+
errorMessage,
|
|
915
|
+
wwwAuthenticateHeaderErrorDescription: errorMessage
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return next({
|
|
920
|
+
context: {
|
|
921
|
+
oidc: id<OidcServerContext<AccessTokenClaims>>(
|
|
922
|
+
id<OidcServerContext.LoggedIn<AccessTokenClaims>>({
|
|
923
|
+
isUserLoggedIn: true,
|
|
924
|
+
accessToken,
|
|
925
|
+
accessTokenClaims
|
|
926
|
+
})
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function oidcRequestMiddleware(params?: {
|
|
934
|
+
assert?: "user logged in";
|
|
935
|
+
hasRequiredClaims?: (params: { accessTokenClaims: AccessTokenClaims }) => Promise<boolean>;
|
|
936
|
+
}) {
|
|
937
|
+
return createMiddleware({ type: "request" }).server<{
|
|
938
|
+
oidc: OidcServerContext<AccessTokenClaims>;
|
|
939
|
+
}>(createFunctionMiddlewareServerFn(params));
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function oidcFnMiddleware(params?: {
|
|
943
|
+
assert?: "user logged in";
|
|
944
|
+
hasRequiredClaims?: (params: { accessTokenClaims: AccessTokenClaims }) => Promise<boolean>;
|
|
945
|
+
}) {
|
|
946
|
+
return createMiddleware({ type: "function" })
|
|
947
|
+
.client(async ({ next }) => {
|
|
948
|
+
const oidc = await getOidc();
|
|
949
|
+
|
|
950
|
+
if (params?.assert === "user logged in" && !oidc.isUserLoggedIn) {
|
|
951
|
+
throw new Error(
|
|
952
|
+
[
|
|
953
|
+
"oidc-spa: You used oidcFnMiddleware({ assert: 'user logged in' })",
|
|
954
|
+
"but the server function the middleware was attached to was called",
|
|
955
|
+
"while the user is not logged in."
|
|
956
|
+
].join(" ")
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (!oidc.isUserLoggedIn) {
|
|
961
|
+
return next();
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return next({
|
|
965
|
+
headers: {
|
|
966
|
+
Authorization: `Bearer ${await oidc.getAccessToken()}`
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
})
|
|
970
|
+
.server<{
|
|
971
|
+
oidc: OidcServerContext<AccessTokenClaims>;
|
|
972
|
+
}>(createFunctionMiddlewareServerFn(params));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// @ts-expect-error
|
|
976
|
+
return {
|
|
977
|
+
createOidcComponent,
|
|
978
|
+
getOidc,
|
|
979
|
+
bootstrapOidc,
|
|
980
|
+
enforceLogin,
|
|
981
|
+
OidcInitializationGate,
|
|
982
|
+
oidcFnMiddleware,
|
|
983
|
+
oidcRequestMiddleware
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const fetchServerEnvVariableValues = createServerFn({ method: "GET" })
|
|
988
|
+
.inputValidator((data: { envVarNames: string[] }) => {
|
|
989
|
+
if (typeof data !== "object" || data === null) {
|
|
990
|
+
throw new Error("Expected an object");
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const { envVarNames } = data as Record<string, unknown>;
|
|
994
|
+
|
|
995
|
+
assert(
|
|
996
|
+
typeGuard<string[]>(
|
|
997
|
+
envVarNames,
|
|
998
|
+
Array.isArray(envVarNames) && envVarNames.every(name => typeof name === "string")
|
|
999
|
+
)
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
return { envVarNames };
|
|
1003
|
+
})
|
|
1004
|
+
.handler(async ({ data }) => {
|
|
1005
|
+
const { envVarNames } = data;
|
|
1006
|
+
return Object.fromEntries(
|
|
1007
|
+
envVarNames.map(envVarName => [envVarName, process.env[envVarName] ?? ""])
|
|
1008
|
+
);
|
|
1009
|
+
});
|