oidc-spa 8.6.18 → 8.7.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 +2 -2
- package/backend.d.ts +3 -20
- package/backend.js +50 -242
- package/backend.js.map +1 -1
- package/core/OidcMetadata.d.ts +2 -2
- package/core/OidcMetadata.js.map +1 -1
- package/core/createOidc.d.ts +2 -4
- package/core/createOidc.js +46 -6
- package/core/createOidc.js.map +1 -1
- package/core/dpop.d.ts +20 -0
- package/core/dpop.js +389 -0
- package/core/dpop.js.map +1 -0
- package/core/earlyInit.js +2 -0
- package/core/earlyInit.js.map +1 -1
- package/core/evtIsUserActive.d.ts +8 -1
- package/core/evtIsUserActive.js +4 -2
- package/core/evtIsUserActive.js.map +1 -1
- package/core/oidcClientTsUserToTokens.d.ts +1 -0
- package/core/oidcClientTsUserToTokens.js +15 -5
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/core/tokenExfiltrationDefense.js +49 -6
- package/core/tokenExfiltrationDefense.js.map +1 -1
- package/esm/angular.d.ts +2 -0
- package/esm/angular.mjs.map +1 -1
- package/esm/backend.d.ts +3 -20
- package/esm/backend.mjs +50 -242
- package/esm/backend.mjs.map +1 -1
- package/esm/core/OidcMetadata.d.ts +2 -2
- package/esm/core/OidcMetadata.mjs.map +1 -1
- package/esm/core/createOidc.d.ts +2 -4
- package/esm/core/createOidc.mjs +46 -6
- package/esm/core/createOidc.mjs.map +1 -1
- package/esm/core/dpop.d.ts +20 -0
- package/esm/core/dpop.mjs +384 -0
- package/esm/core/dpop.mjs.map +1 -0
- package/esm/core/earlyInit.mjs +2 -0
- package/esm/core/earlyInit.mjs.map +1 -1
- package/esm/core/evtIsUserActive.d.ts +8 -1
- package/esm/core/evtIsUserActive.mjs +4 -2
- package/esm/core/evtIsUserActive.mjs.map +1 -1
- package/esm/core/oidcClientTsUserToTokens.d.ts +1 -0
- package/esm/core/oidcClientTsUserToTokens.mjs +15 -5
- package/esm/core/oidcClientTsUserToTokens.mjs.map +1 -1
- package/esm/core/tokenExfiltrationDefense.mjs +49 -6
- package/esm/core/tokenExfiltrationDefense.mjs.map +1 -1
- package/esm/react-spa/createOidcSpaApi.mjs +2 -1
- package/esm/react-spa/createOidcSpaApi.mjs.map +1 -1
- package/esm/react-spa/types.d.ts +2 -0
- package/esm/server/createOidcSpaUtils.d.ts +5 -0
- package/esm/server/createOidcSpaUtils.mjs +639 -0
- package/esm/server/createOidcSpaUtils.mjs.map +1 -0
- package/esm/server/index.d.ts +2 -0
- package/esm/server/index.mjs +3 -0
- package/esm/server/index.mjs.map +1 -0
- package/esm/server/types.d.ts +79 -0
- package/esm/server/types.mjs +2 -0
- package/esm/server/types.mjs.map +1 -0
- package/esm/server/utilsBuilder.d.ts +10 -0
- package/esm/server/utilsBuilder.mjs +13 -0
- package/esm/server/utilsBuilder.mjs.map +1 -0
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +1 -1
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.mjs +102 -94
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.mjs.map +1 -1
- package/esm/tanstack-start/react/createOidcSpaApi.d.ts +2 -2
- package/esm/tanstack-start/react/createOidcSpaApi.mjs +60 -51
- package/esm/tanstack-start/react/createOidcSpaApi.mjs.map +1 -1
- package/esm/tanstack-start/react/index.d.ts +1 -1
- package/esm/tanstack-start/react/index.mjs +2 -2
- package/esm/tanstack-start/react/index.mjs.map +1 -1
- package/esm/tanstack-start/react/types.d.ts +36 -11
- package/esm/tanstack-start/react/{apiBuilder.d.ts → utilsBuilder.d.ts} +9 -9
- package/esm/tanstack-start/react/{apiBuilder.mjs → utilsBuilder.mjs} +6 -6
- package/esm/tanstack-start/react/utilsBuilder.mjs.map +1 -0
- package/esm/tools/generateES256DPoPProof.d.ts +8 -0
- package/esm/tools/generateES256DPoPProof.mjs +48 -0
- package/esm/tools/generateES256DPoPProof.mjs.map +1 -0
- package/esm/tools/getServerDateNow.d.ts +5 -0
- package/esm/tools/getServerDateNow.mjs +7 -0
- package/esm/tools/getServerDateNow.mjs.map +1 -0
- package/esm/tools/startCountdown.mjs +9 -3
- package/esm/tools/startCountdown.mjs.map +1 -1
- package/esm/vendor/{backend → server}/evt.mjs +84 -140
- package/esm/vendor/{backend → server}/jose.mjs +5 -27
- package/esm/vendor/{backend → server}/tsafe.d.ts +1 -0
- package/esm/vendor/{backend → server}/tsafe.mjs +6 -0
- package/esm/vendor/{backend → server}/zod.mjs +196 -50
- package/package.json +6 -1
- package/react-spa/createOidcSpaApi.js +2 -1
- package/react-spa/createOidcSpaApi.js.map +1 -1
- package/react-spa/types.d.ts +2 -0
- package/server/createOidcSpaUtils.d.ts +5 -0
- package/server/createOidcSpaUtils.js +642 -0
- package/server/createOidcSpaUtils.js.map +1 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +6 -0
- package/server/index.js.map +1 -0
- package/server/types.d.ts +79 -0
- package/server/types.js +3 -0
- package/server/types.js.map +1 -0
- package/server/utilsBuilder.d.ts +10 -0
- package/server/utilsBuilder.js +16 -0
- package/server/utilsBuilder.js.map +1 -0
- package/src/angular.ts +3 -0
- package/src/backend.ts +63 -364
- package/src/core/OidcMetadata.ts +4 -2
- package/src/core/createOidc.ts +61 -9
- package/src/core/dpop.ts +583 -0
- package/src/core/earlyInit.ts +3 -0
- package/src/core/evtIsUserActive.ts +11 -5
- package/src/core/oidcClientTsUserToTokens.ts +18 -4
- package/src/core/tokenExfiltrationDefense.ts +60 -5
- package/src/react-spa/createOidcSpaApi.ts +2 -1
- package/src/react-spa/types.tsx +3 -0
- package/src/server/createOidcSpaUtils.ts +848 -0
- package/src/server/index.ts +4 -0
- package/src/server/types.tsx +99 -0
- package/src/server/utilsBuilder.ts +41 -0
- package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +134 -124
- package/src/tanstack-start/react/createOidcSpaApi.ts +73 -69
- package/src/tanstack-start/react/index.ts +2 -2
- package/src/tanstack-start/react/types.tsx +44 -12
- package/src/tanstack-start/react/{apiBuilder.ts → utilsBuilder.ts} +14 -14
- package/src/tools/generateES256DPoPProof.ts +74 -0
- package/src/tools/getServerDateNow.ts +11 -0
- package/src/tools/startCountdown.ts +10 -3
- package/src/vendor/{backend → server}/tsafe.ts +1 -0
- package/tools/generateES256DPoPProof.d.ts +8 -0
- package/tools/generateES256DPoPProof.js +51 -0
- package/tools/generateES256DPoPProof.js.map +1 -0
- package/tools/getServerDateNow.d.ts +5 -0
- package/tools/getServerDateNow.js +10 -0
- package/tools/getServerDateNow.js.map +1 -0
- package/tools/startCountdown.js +9 -3
- package/tools/startCountdown.js.map +1 -1
- package/vendor/server/evt.js +3 -0
- package/vendor/server/jose.js +3 -0
- package/vendor/{backend → server}/tsafe.d.ts +1 -0
- package/vendor/server/tsafe.js +2 -0
- package/vendor/server/zod.js +3 -0
- package/esm/tanstack-start/react/apiBuilder.mjs.map +0 -1
- package/vendor/backend/evt.js +0 -3
- package/vendor/backend/jose.js +0 -3
- package/vendor/backend/tsafe.js +0 -2
- package/vendor/backend/zod.js +0 -3
- /package/esm/vendor/{backend → server}/evt.d.ts +0 -0
- /package/esm/vendor/{backend → server}/jose.d.ts +0 -0
- /package/esm/vendor/{backend → server}/zod.d.ts +0 -0
- /package/src/vendor/{backend → server}/evt.ts +0 -0
- /package/src/vendor/{backend → server}/jose.ts +0 -0
- /package/src/vendor/{backend → server}/zod.ts +0 -0
- /package/vendor/{backend → server}/evt.d.ts +0 -0
- /package/vendor/{backend → server}/jose.d.ts +0 -0
- /package/vendor/{backend → server}/zod.d.ts +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claims defined by RFC 9068: "JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens"
|
|
3
|
+
* https://datatracker.ietf.org/doc/html/rfc9068
|
|
4
|
+
*
|
|
5
|
+
* These tokens are intended for consumption by resource servers.
|
|
6
|
+
*/
|
|
7
|
+
export type DecodedAccessToken_RFC9068 = {
|
|
8
|
+
// --- REQUIRED (MUST) ---
|
|
9
|
+
iss: string; // Issuer Identifier
|
|
10
|
+
sub: string; // Subject Identifier
|
|
11
|
+
aud: string | string[]; // Audience(s)
|
|
12
|
+
exp: number; // Expiration time (seconds since epoch)
|
|
13
|
+
iat: number; // Issued-at time (seconds since epoch)
|
|
14
|
+
|
|
15
|
+
// --- RECOMMENDED (SHOULD) ---
|
|
16
|
+
client_id?: string; // OAuth2 Client ID that requested the token
|
|
17
|
+
scope?: string; // Space-separated list of granted scopes
|
|
18
|
+
jti?: string; // Unique JWT ID (for replay detection)
|
|
19
|
+
|
|
20
|
+
// --- OPTIONAL / EXTENSION CLAIMS ---
|
|
21
|
+
nbf?: number; // Not-before time (standard JWT claim)
|
|
22
|
+
auth_time?: number; // Time of user authentication (optional)
|
|
23
|
+
cnf?: Record<string, unknown>; // Confirmation (e.g. proof-of-possession)
|
|
24
|
+
[key: string]: unknown; // Allow custom claims (e.g. roles, groups)
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ValidateAndDecodeAccessToken<DecodedAccessToken> = (params: {
|
|
28
|
+
request: {
|
|
29
|
+
method: string;
|
|
30
|
+
url: string;
|
|
31
|
+
headers: Record<"Authorization" | "DPoP", string | null | undefined>;
|
|
32
|
+
};
|
|
33
|
+
}) => Promise<ValidateAndDecodeAccessToken.ReturnType<DecodedAccessToken>>;
|
|
34
|
+
|
|
35
|
+
export namespace ValidateAndDecodeAccessToken {
|
|
36
|
+
export type ReturnType<DecodedAccessToken> =
|
|
37
|
+
| (ReturnType.Success<DecodedAccessToken> & { errorCause?: never; debugErrorMessage?: never })
|
|
38
|
+
| (ReturnType.Errored & {
|
|
39
|
+
decodedAccessToken?: never;
|
|
40
|
+
decodedAccessToken_original?: never;
|
|
41
|
+
accessToken?: never;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export namespace ReturnType {
|
|
45
|
+
export type Success<DecodedAccessToken> = {
|
|
46
|
+
isSuccess: true;
|
|
47
|
+
decodedAccessToken: DecodedAccessToken;
|
|
48
|
+
decodedAccessToken_original: DecodedAccessToken_RFC9068;
|
|
49
|
+
accessToken: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type Errored = {
|
|
53
|
+
isSuccess: false;
|
|
54
|
+
errorCause:
|
|
55
|
+
| "missing Authorization header"
|
|
56
|
+
| "validation error"
|
|
57
|
+
| "validation error - access token expired"
|
|
58
|
+
| "validation error - invalid signature";
|
|
59
|
+
debugErrorMessage: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ParamsOfBootstrap<DecodedAccessToken> =
|
|
65
|
+
| ParamsOfBootstrap.Real
|
|
66
|
+
| ParamsOfBootstrap.Mock<DecodedAccessToken>;
|
|
67
|
+
|
|
68
|
+
export namespace ParamsOfBootstrap {
|
|
69
|
+
export type Real = {
|
|
70
|
+
implementation: "real";
|
|
71
|
+
issuerUri: string;
|
|
72
|
+
expectedAudience: string | undefined;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type Mock<DecodedAccessToken> = Mock.DecodeOnly | Mock.UseStaticIdentity<DecodedAccessToken>;
|
|
76
|
+
|
|
77
|
+
export namespace Mock {
|
|
78
|
+
type Common = {
|
|
79
|
+
implementation: "mock";
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type DecodeOnly = Common & {
|
|
83
|
+
behavior: "decode only";
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type UseStaticIdentity<DecodedAccessToken> = Common & {
|
|
87
|
+
behavior: "use static identity";
|
|
88
|
+
decodedAccessToken_mock: DecodedAccessToken;
|
|
89
|
+
decodedAccessToken_original_mock?: DecodedAccessToken_RFC9068;
|
|
90
|
+
accessToken_mock?: string;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type OidcSpaUtils<DecodedAccessToken> = {
|
|
96
|
+
bootstrapAuth: (params: ParamsOfBootstrap<DecodedAccessToken>) => Promise<void>;
|
|
97
|
+
validateAndDecodeAccessToken: ValidateAndDecodeAccessToken<DecodedAccessToken>;
|
|
98
|
+
ofTypeDecodedAccessToken: DecodedAccessToken;
|
|
99
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { OidcSpaUtils } from "./types";
|
|
2
|
+
import type { ZodSchemaLike } from "../tools/ZodSchemaLike";
|
|
3
|
+
import { createOidcSpaUtils } from "./createOidcSpaUtils";
|
|
4
|
+
import type { DecodedAccessToken_RFC9068 } from "./types";
|
|
5
|
+
|
|
6
|
+
export type OidcSpaUtilsBuilder<
|
|
7
|
+
DecodedAccessToken extends Record<string, unknown> = DecodedAccessToken_RFC9068,
|
|
8
|
+
ExcludedMethod extends "withExpectedDecodedAccessTokenShape" | "createUtils" = never
|
|
9
|
+
> = Omit<
|
|
10
|
+
{
|
|
11
|
+
withExpectedDecodedAccessTokenShape: <
|
|
12
|
+
DecodedAccessToken extends Record<string, unknown>
|
|
13
|
+
>(params: {
|
|
14
|
+
decodedAccessTokenSchema: ZodSchemaLike<DecodedAccessToken_RFC9068, DecodedAccessToken>;
|
|
15
|
+
}) => OidcSpaUtilsBuilder<
|
|
16
|
+
DecodedAccessToken,
|
|
17
|
+
ExcludedMethod | "withExpectedDecodedAccessTokenShape"
|
|
18
|
+
>;
|
|
19
|
+
createUtils: () => OidcSpaUtils<DecodedAccessToken>;
|
|
20
|
+
},
|
|
21
|
+
ExcludedMethod
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
function createOidcSpaUtilsBuilder<
|
|
25
|
+
DecodedAccessToken extends Record<string, unknown> = DecodedAccessToken_RFC9068
|
|
26
|
+
>(params: {
|
|
27
|
+
decodedAccessTokenSchema: ZodSchemaLike<DecodedAccessToken_RFC9068, DecodedAccessToken> | undefined;
|
|
28
|
+
}): OidcSpaUtilsBuilder<DecodedAccessToken> {
|
|
29
|
+
return {
|
|
30
|
+
withExpectedDecodedAccessTokenShape: ({ decodedAccessTokenSchema }) =>
|
|
31
|
+
createOidcSpaUtilsBuilder({ decodedAccessTokenSchema }),
|
|
32
|
+
createUtils: () =>
|
|
33
|
+
createOidcSpaUtils<DecodedAccessToken>({
|
|
34
|
+
decodedAccessTokenSchema: params.decodedAccessTokenSchema
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const oidcSpaUtilsBuilder = createOidcSpaUtilsBuilder({
|
|
40
|
+
decodedAccessTokenSchema: undefined
|
|
41
|
+
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
CreateValidateAndGetAccessTokenClaims,
|
|
3
|
+
ParamsOfBootstrap,
|
|
4
|
+
ValidateAndGetAccessTokenClaims
|
|
5
|
+
} from "./types";
|
|
6
|
+
import type { DecodedAccessToken_RFC9068 as AccessTokenClaims_RFC9068 } from "../../server/types";
|
|
3
7
|
import type { ZodSchemaLike } from "../../tools/ZodSchemaLike";
|
|
4
8
|
import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
|
|
5
|
-
import { assert, type Equals, is } from "../../
|
|
9
|
+
import { assert, type Equals, is, id } from "../../vendor/server/tsafe";
|
|
6
10
|
|
|
7
11
|
export function createCreateValidateAndGetAccessTokenClaims_rfc9068<
|
|
8
12
|
AccessTokenClaims extends Record<string, unknown>
|
|
@@ -23,12 +27,84 @@ export function createCreateValidateAndGetAccessTokenClaims_rfc9068<
|
|
|
23
27
|
const createValidateAndGetAccessTokenClaims: CreateValidateAndGetAccessTokenClaims<
|
|
24
28
|
AccessTokenClaims
|
|
25
29
|
> = ({ paramsOfBootstrap }) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const prValidateAndDecodeAccessToken = (async () => {
|
|
31
|
+
const { oidcSpa: oidcSpa_server } = await import("../../server");
|
|
32
|
+
|
|
33
|
+
const { bootstrapAuth, validateAndDecodeAccessToken } =
|
|
34
|
+
accessTokenClaimsSchema === undefined
|
|
35
|
+
? (oidcSpa_server.createUtils() as never)
|
|
36
|
+
: oidcSpa_server
|
|
37
|
+
.withExpectedDecodedAccessTokenShape({
|
|
38
|
+
decodedAccessTokenSchema: accessTokenClaimsSchema
|
|
39
|
+
})
|
|
40
|
+
.createUtils();
|
|
41
|
+
|
|
42
|
+
switch (paramsOfBootstrap.implementation) {
|
|
43
|
+
case "real":
|
|
44
|
+
{
|
|
45
|
+
const expectedAudience = (() => {
|
|
46
|
+
if (expectedAudienceGetter === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const missingEnvNames = new Set<string>();
|
|
51
|
+
|
|
52
|
+
const env_proxy = new Proxy<Record<string, string>>(
|
|
53
|
+
{},
|
|
54
|
+
{
|
|
55
|
+
get: (...[, envName]) => {
|
|
56
|
+
assert(typeof envName === "string");
|
|
57
|
+
|
|
58
|
+
const value = process.env[envName];
|
|
59
|
+
|
|
60
|
+
if (value === undefined) {
|
|
61
|
+
missingEnvNames.add(envName);
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return value;
|
|
66
|
+
},
|
|
67
|
+
has: (...[, envName]) => {
|
|
68
|
+
assert(typeof envName === "string");
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const expectedAudience = expectedAudienceGetter?.({
|
|
75
|
+
paramsOfBootstrap,
|
|
76
|
+
process: { env: env_proxy }
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!expectedAudience) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
[
|
|
82
|
+
"oidc-spa: The expectedAudience() you provided returned empty.",
|
|
83
|
+
"If you specified the expectedAudience in withAccessTokenValidation",
|
|
84
|
+
"it's probably an error.",
|
|
85
|
+
missingEnvNames.size !== 0 &&
|
|
86
|
+
`Did you forget to set the env var: ${Array.from(
|
|
87
|
+
missingEnvNames
|
|
88
|
+
).join(", ")} ?`
|
|
89
|
+
]
|
|
90
|
+
.filter(line => typeof line === "string")
|
|
91
|
+
.join(" ")
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return expectedAudience;
|
|
96
|
+
})();
|
|
97
|
+
|
|
98
|
+
await bootstrapAuth({
|
|
99
|
+
implementation: "real",
|
|
100
|
+
issuerUri: paramsOfBootstrap.issuerUri,
|
|
101
|
+
expectedAudience
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "mock":
|
|
106
|
+
{
|
|
107
|
+
const decodedAccessToken_mock = (() => {
|
|
32
108
|
if (paramsOfBootstrap.accessTokenClaims_mock !== undefined) {
|
|
33
109
|
assert(is<AccessTokenClaims>(paramsOfBootstrap.accessTokenClaims_mock));
|
|
34
110
|
return paramsOfBootstrap.accessTokenClaims_mock;
|
|
@@ -46,131 +122,65 @@ export function createCreateValidateAndGetAccessTokenClaims_rfc9068<
|
|
|
46
122
|
"specify accessTokenClaims_mock when calling bootstrapOidc()"
|
|
47
123
|
].join(" ")
|
|
48
124
|
});
|
|
49
|
-
})()
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
assert<Equals<(typeof paramsOfBootstrap)["implementation"], "real">>;
|
|
55
|
-
|
|
56
|
-
const prVerifyAndDecodeAccessToken = (async () => {
|
|
57
|
-
const { createOidcBackend } = await import("../../backend");
|
|
58
|
-
|
|
59
|
-
const { verifyAndDecodeAccessToken } = await createOidcBackend({
|
|
60
|
-
issuerUri: paramsOfBootstrap.issuerUri,
|
|
61
|
-
decodedAccessTokenSchema: accessTokenClaimsSchema
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return verifyAndDecodeAccessToken;
|
|
65
|
-
})();
|
|
66
|
-
|
|
67
|
-
const expectedAudience = (() => {
|
|
68
|
-
if (expectedAudienceGetter === undefined) {
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const missingEnvNames = new Set<string>();
|
|
125
|
+
})();
|
|
73
126
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const value = process.env[envName];
|
|
81
|
-
|
|
82
|
-
if (value === undefined) {
|
|
83
|
-
missingEnvNames.add(envName);
|
|
84
|
-
return "";
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return value;
|
|
88
|
-
},
|
|
89
|
-
has: (...[, envName]) => {
|
|
90
|
-
assert(typeof envName === "string");
|
|
91
|
-
return true;
|
|
127
|
+
await bootstrapAuth({
|
|
128
|
+
implementation: "mock",
|
|
129
|
+
behavior: "use static identity",
|
|
130
|
+
decodedAccessToken_mock
|
|
131
|
+
});
|
|
92
132
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const expectedAudience = expectedAudienceGetter?.({
|
|
97
|
-
paramsOfBootstrap,
|
|
98
|
-
process: { env: env_proxy }
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (!expectedAudience) {
|
|
102
|
-
throw new Error(
|
|
103
|
-
[
|
|
104
|
-
"oidc-spa: The expectedAudience() you provided returned empty.",
|
|
105
|
-
"If you specified the expectedAudience in withAccessTokenValidation",
|
|
106
|
-
"it's probably and error.",
|
|
107
|
-
missingEnvNames.size !== 0 &&
|
|
108
|
-
`Did you forget to set the env var: ${Array.from(missingEnvNames).join(
|
|
109
|
-
", "
|
|
110
|
-
)} ?`
|
|
111
|
-
]
|
|
112
|
-
.filter(line => typeof line === "string")
|
|
113
|
-
.join(" ")
|
|
114
|
-
);
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
assert<Equals<typeof paramsOfBootstrap, never>>(false);
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
return
|
|
138
|
+
return validateAndDecodeAccessToken;
|
|
118
139
|
})();
|
|
119
140
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
wwwAuthenticateHeaderErrorDescription: (() => {
|
|
137
|
-
switch (errorCase) {
|
|
138
|
-
case "does not respect schema":
|
|
139
|
-
return "The access token is malformed or missing required claims";
|
|
140
|
-
case "expired":
|
|
141
|
-
return "The access token expired";
|
|
142
|
-
case "invalid signature":
|
|
143
|
-
return "The access token signature is invalid";
|
|
144
|
-
}
|
|
145
|
-
})()
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (expectedAudience !== undefined) {
|
|
150
|
-
const aud_array =
|
|
151
|
-
typeof decodedAccessToken_original.aud === "string"
|
|
152
|
-
? [decodedAccessToken_original.aud]
|
|
153
|
-
: decodedAccessToken_original.aud;
|
|
154
|
-
|
|
155
|
-
if (!aud_array.includes(expectedAudience)) {
|
|
156
|
-
return {
|
|
157
|
-
isValid: false,
|
|
158
|
-
errorMessage: [
|
|
159
|
-
"Access token is not for the expected audience.",
|
|
160
|
-
`Got aud claim: ${JSON.stringify(decodedAccessToken_original.aud)}`,
|
|
161
|
-
`Expected: ${expectedAudience}`
|
|
162
|
-
].join(" "),
|
|
163
|
-
wwwAuthenticateHeaderErrorDescription: "The access token audience is invalid"
|
|
164
|
-
};
|
|
165
|
-
}
|
|
141
|
+
const validateAndGetAccessTokenClaims: ValidateAndGetAccessTokenClaims<
|
|
142
|
+
AccessTokenClaims
|
|
143
|
+
> = async ({ request }) => {
|
|
144
|
+
const validateAndDecodeAccessToken = await prValidateAndDecodeAccessToken;
|
|
145
|
+
|
|
146
|
+
const { isSuccess, errorCause, debugErrorMessage, decodedAccessToken, accessToken } =
|
|
147
|
+
await validateAndDecodeAccessToken({
|
|
148
|
+
request
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!isSuccess) {
|
|
152
|
+
if (errorCause === "missing Authorization header") {
|
|
153
|
+
return id<ValidateAndGetAccessTokenClaims.ReturnType.Errored.AnonymousRequest>({
|
|
154
|
+
isSuccess: false,
|
|
155
|
+
isAnonymousRequest: true
|
|
156
|
+
});
|
|
166
157
|
}
|
|
167
158
|
|
|
168
|
-
return {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
return id<ValidateAndGetAccessTokenClaims.ReturnType.Errored.ValidationFailed>({
|
|
160
|
+
isSuccess: false,
|
|
161
|
+
isAnonymousRequest: false,
|
|
162
|
+
debugErrorMessage: `${errorCause}: ${debugErrorMessage}`,
|
|
163
|
+
wwwAuthenticateResponseHeaderValue: `Bearer error="invalid_token", error_description="${(() => {
|
|
164
|
+
switch (errorCause) {
|
|
165
|
+
case "validation error":
|
|
166
|
+
case "validation error - invalid signature":
|
|
167
|
+
case "validation error - access token expired":
|
|
168
|
+
return "Validation Failed";
|
|
169
|
+
default:
|
|
170
|
+
assert<Equals<typeof errorCause, never>>(false);
|
|
171
|
+
}
|
|
172
|
+
})()}"`
|
|
173
|
+
});
|
|
172
174
|
}
|
|
175
|
+
|
|
176
|
+
return id<ValidateAndGetAccessTokenClaims.ReturnType.Success<AccessTokenClaims>>({
|
|
177
|
+
isSuccess: true,
|
|
178
|
+
accessTokenClaims: decodedAccessToken,
|
|
179
|
+
accessToken
|
|
180
|
+
});
|
|
173
181
|
};
|
|
182
|
+
|
|
183
|
+
return { validateAndGetAccessTokenClaims };
|
|
174
184
|
};
|
|
175
185
|
|
|
176
186
|
return { createValidateAndGetAccessTokenClaims };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useReducer } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
CreateValidateAndGetAccessTokenClaims,
|
|
4
|
-
|
|
4
|
+
OidcSpaUtils,
|
|
5
5
|
UseOidc,
|
|
6
6
|
GetOidc,
|
|
7
7
|
ParamsOfBootstrap,
|
|
@@ -21,6 +21,7 @@ import type { GetterOrDirectValue } from "../../tools/GetterOrDirectValue";
|
|
|
21
21
|
import { createServerFn, createMiddleware } from "@tanstack/react-start";
|
|
22
22
|
// @ts-expect-error: Since our module is not labeled as ESM we don't have the types here.
|
|
23
23
|
import { getRequest, setResponseHeader, setResponseStatus } from "@tanstack/react-start/server";
|
|
24
|
+
//import { getRequest, setResponseHeader, setResponseStatus } from "@tanstack/react-start-server";
|
|
24
25
|
import { toFullyQualifiedUrl } from "../../tools/toFullyQualifiedUrl";
|
|
25
26
|
import { BEFORE_LOAD_FN_BRAND_PROPERTY_NAME } from "./disableSsrIfLoginEnforced";
|
|
26
27
|
import { setDesiredPostLoginRedirectUrl } from "../../core/desiredPostLoginRedirectUrl";
|
|
@@ -40,7 +41,7 @@ export function createOidcSpaApi<
|
|
|
40
41
|
createValidateAndGetAccessTokenClaims:
|
|
41
42
|
| CreateValidateAndGetAccessTokenClaims<AccessTokenClaims>
|
|
42
43
|
| undefined;
|
|
43
|
-
}):
|
|
44
|
+
}): OidcSpaUtils<AutoLogin, DecodedIdToken, AccessTokenClaims> {
|
|
44
45
|
const {
|
|
45
46
|
autoLogin,
|
|
46
47
|
decodedIdTokenSchema,
|
|
@@ -667,7 +668,8 @@ export function createOidcSpaApi<
|
|
|
667
668
|
__metadata: paramsOfBootstrap.__metadata,
|
|
668
669
|
__unsafe_useIdTokenAsAccessToken:
|
|
669
670
|
paramsOfBootstrap.__unsafe_useIdTokenAsAccessToken,
|
|
670
|
-
autoLogoutParams: paramsOfBootstrap.autoLogoutParams
|
|
671
|
+
autoLogoutParams: paramsOfBootstrap.autoLogoutParams,
|
|
672
|
+
dpop: paramsOfBootstrap.dpop
|
|
671
673
|
});
|
|
672
674
|
} catch (error) {
|
|
673
675
|
if (!(error instanceof OidcInitializationError)) {
|
|
@@ -786,84 +788,86 @@ export function createOidcSpaApi<
|
|
|
786
788
|
const { next } = options;
|
|
787
789
|
|
|
788
790
|
const unauthorized = (params: {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
+
code: 401 | 403;
|
|
792
|
+
wwwAuthenticateResponseHeaderValue: string;
|
|
793
|
+
debugErrorMessage: string;
|
|
791
794
|
}) => {
|
|
792
|
-
const {
|
|
793
|
-
|
|
794
|
-
setResponseHeader(
|
|
795
|
-
|
|
796
|
-
|
|
795
|
+
const { code, wwwAuthenticateResponseHeaderValue, debugErrorMessage } = params;
|
|
796
|
+
|
|
797
|
+
setResponseHeader("WWW-Authenticate", wwwAuthenticateResponseHeaderValue);
|
|
798
|
+
setResponseStatus(
|
|
799
|
+
code,
|
|
800
|
+
(() => {
|
|
801
|
+
switch (code) {
|
|
802
|
+
case 401:
|
|
803
|
+
return "Unauthorized";
|
|
804
|
+
case 403:
|
|
805
|
+
return "Forbidden";
|
|
806
|
+
default:
|
|
807
|
+
assert<Equals<typeof code, never>>(false);
|
|
808
|
+
}
|
|
809
|
+
})()
|
|
797
810
|
);
|
|
798
|
-
setResponseStatus(401, "Unauthorized");
|
|
799
811
|
|
|
800
|
-
|
|
812
|
+
if (process.env.NODE_ENV === "development") {
|
|
813
|
+
console.error(`oidc-spa: ${debugErrorMessage}`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return new Error(`oidc-spa: ${wwwAuthenticateResponseHeaderValue}`);
|
|
801
817
|
};
|
|
802
818
|
|
|
803
|
-
|
|
819
|
+
assert(prValidateAndGetAccessTokenClaims !== undefined);
|
|
804
820
|
|
|
805
|
-
const
|
|
821
|
+
const { validateAndGetAccessTokenClaims } = await prValidateAndGetAccessTokenClaims;
|
|
806
822
|
|
|
807
|
-
|
|
808
|
-
if (params?.assert === "user logged in") {
|
|
809
|
-
const errorMessage = [
|
|
810
|
-
"Asserted user logged in for that serverFn request",
|
|
811
|
-
"but no access token was attached to the request"
|
|
812
|
-
].join(" ");
|
|
823
|
+
const { headers, url, method } = getRequest();
|
|
813
824
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
825
|
+
const resultOfValidate = await validateAndGetAccessTokenClaims({
|
|
826
|
+
request: {
|
|
827
|
+
url,
|
|
828
|
+
method,
|
|
829
|
+
headers: {
|
|
830
|
+
Authorization: headers.get("Authorization"),
|
|
831
|
+
DPoP: headers.get("DPoP")
|
|
832
|
+
}
|
|
818
833
|
}
|
|
834
|
+
});
|
|
819
835
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
836
|
+
if (!resultOfValidate.isSuccess) {
|
|
837
|
+
if (resultOfValidate.isAnonymousRequest) {
|
|
838
|
+
if (params?.assert === "user logged in") {
|
|
839
|
+
throw unauthorized({
|
|
840
|
+
code: 401,
|
|
841
|
+
wwwAuthenticateResponseHeaderValue:
|
|
842
|
+
'Bearer error="invalid_request", error_description="Missing access token"',
|
|
843
|
+
debugErrorMessage: [
|
|
844
|
+
"Asserted user logged in for that serverFn request",
|
|
845
|
+
"but no access token was attached to the request"
|
|
846
|
+
].join(" ")
|
|
847
|
+
});
|
|
827
848
|
}
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
849
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
850
|
+
return next({
|
|
851
|
+
context: {
|
|
852
|
+
oidc: id<OidcServerContext<AccessTokenClaims>>(
|
|
853
|
+
id<OidcServerContext.NotLoggedIn>({
|
|
854
|
+
isUserLoggedIn: false
|
|
855
|
+
})
|
|
856
|
+
)
|
|
857
|
+
}
|
|
858
|
+
});
|
|
836
859
|
}
|
|
837
860
|
|
|
838
|
-
|
|
839
|
-
})();
|
|
840
|
-
|
|
841
|
-
if (accessToken === undefined) {
|
|
842
|
-
const errorMessage =
|
|
843
|
-
"Missing well formed Authorization header with Bearer <access_token>";
|
|
844
|
-
|
|
845
|
-
throw unauthorized({
|
|
846
|
-
errorMessage,
|
|
847
|
-
wwwAuthenticateHeaderErrorDescription: errorMessage
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
assert(prValidateAndGetAccessTokenClaims !== undefined);
|
|
852
|
-
|
|
853
|
-
const { validateAndGetAccessTokenClaims } = await prValidateAndGetAccessTokenClaims;
|
|
854
|
-
|
|
855
|
-
const resultOfValidate = await validateAndGetAccessTokenClaims({ accessToken });
|
|
856
|
-
|
|
857
|
-
if (!resultOfValidate.isValid) {
|
|
858
|
-
const { errorMessage, wwwAuthenticateHeaderErrorDescription } = resultOfValidate;
|
|
861
|
+
const { debugErrorMessage, wwwAuthenticateResponseHeaderValue } = resultOfValidate;
|
|
859
862
|
|
|
860
863
|
throw unauthorized({
|
|
861
|
-
|
|
862
|
-
|
|
864
|
+
code: 401,
|
|
865
|
+
wwwAuthenticateResponseHeaderValue,
|
|
866
|
+
debugErrorMessage
|
|
863
867
|
});
|
|
864
868
|
}
|
|
865
869
|
|
|
866
|
-
const { accessTokenClaims } = resultOfValidate;
|
|
870
|
+
const { accessTokenClaims, accessToken } = resultOfValidate;
|
|
867
871
|
|
|
868
872
|
assert(is<Exclude<AccessTokenClaims, undefined>>(accessTokenClaims));
|
|
869
873
|
|
|
@@ -900,14 +904,14 @@ export function createOidcSpaApi<
|
|
|
900
904
|
break check_required_claims;
|
|
901
905
|
}
|
|
902
906
|
|
|
903
|
-
const errorMessage = [
|
|
904
|
-
"Missing or invalid required access token claim.",
|
|
905
|
-
`Related to claims: ${Array.from(accessedClaimNames).join(" and/or ")}`
|
|
906
|
-
].join(" ");
|
|
907
|
-
|
|
908
907
|
throw unauthorized({
|
|
909
|
-
|
|
910
|
-
|
|
908
|
+
code: 403,
|
|
909
|
+
wwwAuthenticateResponseHeaderValue:
|
|
910
|
+
'Bearer error="insufficient_scope", error_description="Insufficient privileges"',
|
|
911
|
+
debugErrorMessage: [
|
|
912
|
+
"Missing or invalid required access token claim.",
|
|
913
|
+
`Related to claims: ${Array.from(accessedClaimNames).join(" and/or ")}`
|
|
914
|
+
].join(" ")
|
|
911
915
|
});
|
|
912
916
|
}
|
|
913
917
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { __disableSsrIfLoginEnforced } from "./disableSsrIfLoginEnforced";
|
|
2
2
|
export { __withOidcSpaServerEntry } from "./withOidcSpaServerEntry";
|
|
3
3
|
export type * from "./types";
|
|
4
|
-
import {
|
|
4
|
+
import { oidcSpaUtilsBuilder } from "./utilsBuilder";
|
|
5
5
|
|
|
6
|
-
export const oidcSpa =
|
|
6
|
+
export const oidcSpa = oidcSpaUtilsBuilder;
|