@better-auth/core 1.7.0-beta.3 → 1.7.0-beta.4
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/dist/context/global.mjs +1 -1
- package/dist/db/adapter/factory.mjs +63 -1
- package/dist/db/adapter/index.d.mts +35 -1
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/type.d.mts +12 -0
- package/dist/error/codes.d.mts +1 -0
- package/dist/error/codes.mjs +1 -0
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/authorization-params.d.mts +12 -0
- package/dist/oauth2/authorization-params.mjs +12 -0
- package/dist/oauth2/basic-credentials.d.mts +30 -0
- package/dist/oauth2/basic-credentials.mjs +64 -0
- package/dist/oauth2/client-assertion.d.mts +38 -22
- package/dist/oauth2/client-assertion.mjs +63 -28
- package/dist/oauth2/client-credentials-token.d.mts +19 -40
- package/dist/oauth2/client-credentials-token.mjs +18 -29
- package/dist/oauth2/create-authorization-url.d.mts +9 -1
- package/dist/oauth2/create-authorization-url.mjs +23 -5
- package/dist/oauth2/index.d.mts +10 -7
- package/dist/oauth2/index.mjs +9 -7
- package/dist/oauth2/oauth-provider.d.mts +21 -2
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +19 -32
- package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
- package/dist/oauth2/token-endpoint-auth.mjs +89 -0
- package/dist/oauth2/utils.d.mts +9 -1
- package/dist/oauth2/utils.mjs +12 -1
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +17 -30
- package/dist/oauth2/verify.mjs +15 -5
- package/dist/social-providers/apple.d.mts +4 -18
- package/dist/social-providers/apple.mjs +14 -3
- package/dist/social-providers/atlassian.d.mts +3 -1
- package/dist/social-providers/atlassian.mjs +5 -2
- package/dist/social-providers/cognito.d.mts +16 -1
- package/dist/social-providers/cognito.mjs +6 -2
- package/dist/social-providers/discord.d.mts +4 -2
- package/dist/social-providers/discord.mjs +16 -3
- package/dist/social-providers/dropbox.d.mts +3 -1
- package/dist/social-providers/dropbox.mjs +5 -4
- package/dist/social-providers/facebook.d.mts +3 -1
- package/dist/social-providers/facebook.mjs +5 -2
- package/dist/social-providers/figma.d.mts +3 -1
- package/dist/social-providers/figma.mjs +3 -2
- package/dist/social-providers/github.d.mts +3 -1
- package/dist/social-providers/github.mjs +5 -4
- package/dist/social-providers/gitlab.d.mts +3 -1
- package/dist/social-providers/gitlab.mjs +3 -2
- package/dist/social-providers/google.d.mts +3 -1
- package/dist/social-providers/google.mjs +5 -2
- package/dist/social-providers/huggingface.d.mts +3 -1
- package/dist/social-providers/huggingface.mjs +3 -2
- package/dist/social-providers/index.d.mts +104 -36
- package/dist/social-providers/kakao.d.mts +3 -1
- package/dist/social-providers/kakao.mjs +3 -2
- package/dist/social-providers/kick.d.mts +3 -1
- package/dist/social-providers/kick.mjs +3 -2
- package/dist/social-providers/line.d.mts +3 -1
- package/dist/social-providers/line.mjs +3 -2
- package/dist/social-providers/linear.d.mts +3 -1
- package/dist/social-providers/linear.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +3 -1
- package/dist/social-providers/linkedin.mjs +3 -2
- package/dist/social-providers/microsoft-entra-id.d.mts +2 -1
- package/dist/social-providers/microsoft-entra-id.mjs +3 -2
- package/dist/social-providers/naver.d.mts +3 -1
- package/dist/social-providers/naver.mjs +3 -2
- package/dist/social-providers/notion.d.mts +3 -1
- package/dist/social-providers/notion.mjs +5 -2
- package/dist/social-providers/paybin.d.mts +3 -1
- package/dist/social-providers/paybin.mjs +3 -2
- package/dist/social-providers/paypal.d.mts +3 -1
- package/dist/social-providers/paypal.mjs +4 -3
- package/dist/social-providers/polar.d.mts +3 -1
- package/dist/social-providers/polar.mjs +3 -2
- package/dist/social-providers/railway.d.mts +3 -1
- package/dist/social-providers/railway.mjs +3 -2
- package/dist/social-providers/reddit.d.mts +3 -1
- package/dist/social-providers/reddit.mjs +3 -2
- package/dist/social-providers/roblox.d.mts +4 -2
- package/dist/social-providers/roblox.mjs +12 -2
- package/dist/social-providers/salesforce.d.mts +3 -1
- package/dist/social-providers/salesforce.mjs +3 -2
- package/dist/social-providers/slack.d.mts +4 -2
- package/dist/social-providers/slack.mjs +11 -8
- package/dist/social-providers/spotify.d.mts +3 -1
- package/dist/social-providers/spotify.mjs +3 -2
- package/dist/social-providers/tiktok.d.mts +3 -1
- package/dist/social-providers/tiktok.mjs +14 -2
- package/dist/social-providers/twitch.d.mts +3 -1
- package/dist/social-providers/twitch.mjs +3 -2
- package/dist/social-providers/twitter.d.mts +5 -2
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/vercel.d.mts +3 -1
- package/dist/social-providers/vercel.mjs +3 -2
- package/dist/social-providers/vk.d.mts +3 -1
- package/dist/social-providers/vk.mjs +3 -2
- package/dist/social-providers/wechat.d.mts +3 -1
- package/dist/social-providers/wechat.mjs +7 -1
- package/dist/social-providers/zoom.d.mts +4 -2
- package/dist/social-providers/zoom.mjs +10 -17
- package/dist/types/context.d.mts +23 -3
- package/dist/types/init-options.d.mts +29 -5
- package/dist/utils/ip.d.mts +5 -4
- package/dist/utils/ip.mjs +3 -3
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +5 -1
- package/dist/utils/string.mjs +20 -1
- package/dist/utils/url.d.mts +18 -1
- package/dist/utils/url.mjs +30 -1
- package/package.json +9 -8
- package/src/db/adapter/factory.ts +118 -0
- package/src/db/adapter/index.ts +32 -0
- package/src/db/adapter/types.ts +1 -0
- package/src/db/type.ts +12 -0
- package/src/error/codes.ts +1 -0
- package/src/oauth2/authorization-params.ts +28 -0
- package/src/oauth2/basic-credentials.ts +87 -0
- package/src/oauth2/client-assertion.ts +131 -58
- package/src/oauth2/client-credentials-token.ts +48 -72
- package/src/oauth2/create-authorization-url.ts +28 -6
- package/src/oauth2/index.ts +25 -9
- package/src/oauth2/oauth-provider.ts +21 -2
- package/src/oauth2/refresh-access-token.ts +50 -76
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +19 -0
- package/src/oauth2/validate-authorization-code.ts +55 -85
- package/src/oauth2/verify.ts +20 -4
- package/src/social-providers/apple.ts +26 -2
- package/src/social-providers/atlassian.ts +8 -1
- package/src/social-providers/cognito.ts +26 -1
- package/src/social-providers/discord.ts +21 -17
- package/src/social-providers/dropbox.ts +7 -5
- package/src/social-providers/facebook.ts +11 -6
- package/src/social-providers/figma.ts +8 -1
- package/src/social-providers/github.ts +4 -2
- package/src/social-providers/gitlab.ts +2 -0
- package/src/social-providers/google.ts +2 -0
- package/src/social-providers/huggingface.ts +8 -1
- package/src/social-providers/kakao.ts +2 -1
- package/src/social-providers/kick.ts +8 -1
- package/src/social-providers/line.ts +2 -0
- package/src/social-providers/linear.ts +8 -1
- package/src/social-providers/linkedin.ts +2 -0
- package/src/social-providers/microsoft-entra-id.ts +1 -0
- package/src/social-providers/naver.ts +2 -1
- package/src/social-providers/notion.ts +8 -1
- package/src/social-providers/paybin.ts +2 -0
- package/src/social-providers/paypal.ts +7 -1
- package/src/social-providers/polar.ts +8 -1
- package/src/social-providers/railway.ts +8 -1
- package/src/social-providers/reddit.ts +2 -1
- package/src/social-providers/roblox.ts +16 -11
- package/src/social-providers/salesforce.ts +8 -1
- package/src/social-providers/slack.ts +15 -9
- package/src/social-providers/spotify.ts +8 -1
- package/src/social-providers/tiktok.ts +22 -9
- package/src/social-providers/twitch.ts +2 -1
- package/src/social-providers/twitter.ts +1 -0
- package/src/social-providers/vercel.ts +8 -1
- package/src/social-providers/vk.ts +8 -1
- package/src/social-providers/wechat.ts +9 -1
- package/src/social-providers/zoom.ts +15 -19
- package/src/types/context.ts +25 -4
- package/src/types/init-options.ts +29 -5
- package/src/utils/ip.ts +12 -13
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +28 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { JWTHeaderParameters } from "jose";
|
|
2
2
|
import { importJWK, importPKCS8, SignJWT } from "jose";
|
|
3
|
+
import type { Awaitable } from "../types";
|
|
3
4
|
|
|
4
5
|
/** Asymmetric signing algorithms compatible with private_key_jwt (RFC 7523). */
|
|
5
|
-
export const
|
|
6
|
+
export const PRIVATE_KEY_JWT_SIGNING_ALGORITHMS = [
|
|
6
7
|
"RS256",
|
|
7
8
|
"RS384",
|
|
8
9
|
"RS512",
|
|
@@ -15,15 +16,79 @@ export const ASSERTION_SIGNING_ALGORITHMS = [
|
|
|
15
16
|
"EdDSA",
|
|
16
17
|
] as const;
|
|
17
18
|
|
|
18
|
-
export type
|
|
19
|
-
(typeof
|
|
19
|
+
export type PrivateKeyJwtSigningAlgorithm =
|
|
20
|
+
(typeof PRIVATE_KEY_JWT_SIGNING_ALGORITHMS)[number];
|
|
21
|
+
|
|
22
|
+
function assertSupportedPrivateKeyJwtAlgorithm(
|
|
23
|
+
candidate: string,
|
|
24
|
+
): asserts candidate is PrivateKeyJwtSigningAlgorithm {
|
|
25
|
+
if (
|
|
26
|
+
!(PRIVATE_KEY_JWT_SIGNING_ALGORITHMS as readonly string[]).includes(
|
|
27
|
+
candidate,
|
|
28
|
+
)
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Unsupported private_key_jwt signing algorithm: ${candidate}. Use one of ${PRIVATE_KEY_JWT_SIGNING_ALGORITHMS.join(", ")}.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validates `private_key_jwt` options eagerly and returns the algorithm to
|
|
38
|
+
* use for signing.
|
|
39
|
+
*
|
|
40
|
+
* Asserts that key material is configured, that any explicit `algorithm` is
|
|
41
|
+
* supported, that any JWK-embedded `alg` is supported, and that the two
|
|
42
|
+
* agree when both are set.
|
|
43
|
+
*/
|
|
44
|
+
function resolveValidPrivateKeyJwtOptions(options: {
|
|
45
|
+
privateKeyJwk?: JsonWebKey;
|
|
46
|
+
privateKeyPem?: string;
|
|
47
|
+
algorithm?: PrivateKeyJwtSigningAlgorithm;
|
|
48
|
+
}): PrivateKeyJwtSigningAlgorithm {
|
|
49
|
+
if (!options.privateKeyJwk && !options.privateKeyPem) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"private_key_jwt requires either privateKeyJwk or privateKeyPem",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (options.algorithm) {
|
|
55
|
+
assertSupportedPrivateKeyJwtAlgorithm(options.algorithm);
|
|
56
|
+
}
|
|
57
|
+
const jwkAlg = options.privateKeyJwk?.alg;
|
|
58
|
+
if (typeof jwkAlg === "string") {
|
|
59
|
+
assertSupportedPrivateKeyJwtAlgorithm(jwkAlg);
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
options.algorithm &&
|
|
63
|
+
typeof jwkAlg === "string" &&
|
|
64
|
+
options.algorithm !== jwkAlg
|
|
65
|
+
) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`JWK alg "${jwkAlg}" does not match configured algorithm "${options.algorithm}". Remove the JWK alg field, or pass an algorithm that matches the JWK.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return options.algorithm ?? (typeof jwkAlg === "string" ? jwkAlg : "RS256");
|
|
71
|
+
}
|
|
20
72
|
|
|
21
73
|
export const CLIENT_ASSERTION_TYPE =
|
|
22
74
|
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
23
75
|
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
76
|
+
export type ClientAssertionGrantType =
|
|
77
|
+
| "authorization_code"
|
|
78
|
+
| "refresh_token"
|
|
79
|
+
| "client_credentials";
|
|
80
|
+
|
|
81
|
+
export interface ClientAssertionContext {
|
|
82
|
+
clientId: string;
|
|
83
|
+
tokenEndpoint: string;
|
|
84
|
+
grantType: ClientAssertionGrantType;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type ClientAssertionGetter = (
|
|
88
|
+
context: ClientAssertionContext,
|
|
89
|
+
) => Awaitable<string>;
|
|
90
|
+
|
|
91
|
+
export interface PrivateKeyJwtClientAssertionGetterOptions {
|
|
27
92
|
/** Private key in JWK format for signing. */
|
|
28
93
|
privateKeyJwk?: JsonWebKey;
|
|
29
94
|
/** Private key in PKCS#8 PEM format for signing. */
|
|
@@ -31,9 +96,7 @@ export interface ClientAssertionConfig {
|
|
|
31
96
|
/** Key ID to include in the JWT header. */
|
|
32
97
|
kid?: string;
|
|
33
98
|
/** Asymmetric signing algorithm. Symmetric algorithms (HS256) and "none" are not allowed. @default "RS256" */
|
|
34
|
-
algorithm?:
|
|
35
|
-
/** Token endpoint URL (used as the JWT `aud` claim). */
|
|
36
|
-
tokenEndpoint?: string;
|
|
99
|
+
algorithm?: PrivateKeyJwtSigningAlgorithm;
|
|
37
100
|
/** Assertion lifetime in seconds. @default 120 */
|
|
38
101
|
expiresIn?: number;
|
|
39
102
|
}
|
|
@@ -41,10 +104,16 @@ export interface ClientAssertionConfig {
|
|
|
41
104
|
/**
|
|
42
105
|
* Signs an RFC 7523 client assertion JWT for `private_key_jwt` authentication.
|
|
43
106
|
*
|
|
44
|
-
* The JWT contains
|
|
45
|
-
*
|
|
107
|
+
* The JWT contains these claims:
|
|
108
|
+
*
|
|
109
|
+
* - iss=clientId
|
|
110
|
+
* - sub=clientId
|
|
111
|
+
* - aud=tokenEndpoint
|
|
112
|
+
* - exp=now + 120s
|
|
113
|
+
* - jti=unique
|
|
114
|
+
* - iat=now
|
|
46
115
|
*/
|
|
47
|
-
export async function
|
|
116
|
+
export async function signPrivateKeyJwtClientAssertion({
|
|
48
117
|
clientId,
|
|
49
118
|
tokenEndpoint,
|
|
50
119
|
privateKeyJwk,
|
|
@@ -58,34 +127,30 @@ export async function signClientAssertion({
|
|
|
58
127
|
privateKeyJwk?: JsonWebKey;
|
|
59
128
|
privateKeyPem?: string;
|
|
60
129
|
kid?: string;
|
|
61
|
-
algorithm?:
|
|
130
|
+
algorithm?: PrivateKeyJwtSigningAlgorithm;
|
|
62
131
|
expiresIn?: number;
|
|
63
132
|
}): Promise<string> {
|
|
64
|
-
|
|
65
|
-
|
|
133
|
+
const resolvedAlg = resolveValidPrivateKeyJwtOptions({
|
|
134
|
+
privateKeyJwk,
|
|
135
|
+
privateKeyPem,
|
|
136
|
+
algorithm,
|
|
137
|
+
});
|
|
138
|
+
// Fall back to the JWK-embedded kid when not explicitly provided (RFC 7517).
|
|
139
|
+
// JsonWebKey types include alg but not kid; access kid via index.
|
|
66
140
|
const jwk = privateKeyJwk as Record<string, unknown> | undefined;
|
|
67
141
|
const resolvedKid = kid ?? (jwk?.kid as string | undefined);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
(privateKeyJwk
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let key: Awaited<ReturnType<typeof importJWK>>;
|
|
74
|
-
if (privateKeyJwk) {
|
|
75
|
-
key = await importJWK(privateKeyJwk, resolvedAlg);
|
|
76
|
-
} else if (privateKeyPem) {
|
|
77
|
-
key = await importPKCS8(privateKeyPem, resolvedAlg);
|
|
78
|
-
} else {
|
|
79
|
-
throw new Error(
|
|
80
|
-
"private_key_jwt requires either privateKeyJwk or privateKeyPem",
|
|
81
|
-
);
|
|
82
|
-
}
|
|
142
|
+
|
|
143
|
+
const key: Awaited<ReturnType<typeof importJWK>> = privateKeyJwk
|
|
144
|
+
? await importJWK(privateKeyJwk, resolvedAlg)
|
|
145
|
+
: await importPKCS8(privateKeyPem as string, resolvedAlg);
|
|
83
146
|
|
|
84
147
|
const now = Math.floor(Date.now() / 1000);
|
|
85
148
|
const jti = crypto.randomUUID();
|
|
86
149
|
|
|
87
150
|
const header: JWTHeaderParameters = { alg: resolvedAlg, typ: "JWT" };
|
|
88
|
-
if (resolvedKid)
|
|
151
|
+
if (resolvedKid) {
|
|
152
|
+
header.kid = resolvedKid;
|
|
153
|
+
}
|
|
89
154
|
|
|
90
155
|
return new SignJWT({})
|
|
91
156
|
.setProtectedHeader(header)
|
|
@@ -99,36 +164,44 @@ export async function signClientAssertion({
|
|
|
99
164
|
}
|
|
100
165
|
|
|
101
166
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
167
|
+
* Creates a client assertion getter for `private_key_jwt` authentication.
|
|
168
|
+
*
|
|
169
|
+
* Validates options eagerly (key material, supported algorithm, JWK alg
|
|
170
|
+
* agreement) so misconfiguration surfaces at construction rather than on the
|
|
171
|
+
* first token request. The returned function signs a fresh RFC 7523 JWT
|
|
172
|
+
* assertion for every token endpoint request.
|
|
104
173
|
*/
|
|
105
|
-
export
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!assertion) {
|
|
116
|
-
const audEndpoint = tokenEndpoint ?? clientAssertion.tokenEndpoint;
|
|
117
|
-
if (!audEndpoint) {
|
|
118
|
-
throw new Error(
|
|
119
|
-
"private_key_jwt requires a tokenEndpoint for the JWT audience claim",
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
assertion = await signClientAssertion({
|
|
174
|
+
export function createPrivateKeyJwtClientAssertionGetter(
|
|
175
|
+
options: PrivateKeyJwtClientAssertionGetterOptions,
|
|
176
|
+
): ClientAssertionGetter {
|
|
177
|
+
resolveValidPrivateKeyJwtOptions({
|
|
178
|
+
privateKeyJwk: options.privateKeyJwk,
|
|
179
|
+
privateKeyPem: options.privateKeyPem,
|
|
180
|
+
algorithm: options.algorithm,
|
|
181
|
+
});
|
|
182
|
+
return ({ clientId, tokenEndpoint }) =>
|
|
183
|
+
signPrivateKeyJwtClientAssertion({
|
|
123
184
|
clientId,
|
|
124
|
-
tokenEndpoint
|
|
125
|
-
privateKeyJwk:
|
|
126
|
-
privateKeyPem:
|
|
127
|
-
kid:
|
|
128
|
-
algorithm:
|
|
129
|
-
expiresIn:
|
|
185
|
+
tokenEndpoint,
|
|
186
|
+
privateKeyJwk: options.privateKeyJwk,
|
|
187
|
+
privateKeyPem: options.privateKeyPem,
|
|
188
|
+
kid: options.kid,
|
|
189
|
+
algorithm: options.algorithm,
|
|
190
|
+
expiresIn: options.expiresIn,
|
|
130
191
|
});
|
|
131
|
-
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Resolves a client assertion getter into `client_assertion` + `client_assertion_type` params for injection into a token request body.
|
|
196
|
+
*/
|
|
197
|
+
export async function resolveClientAssertionParams({
|
|
198
|
+
getClientAssertion,
|
|
199
|
+
context,
|
|
200
|
+
}: {
|
|
201
|
+
getClientAssertion: ClientAssertionGetter;
|
|
202
|
+
context: ClientAssertionContext;
|
|
203
|
+
}): Promise<Record<string, string>> {
|
|
204
|
+
const assertion = await getClientAssertion(context);
|
|
132
205
|
return {
|
|
133
206
|
client_assertion: assertion,
|
|
134
207
|
client_assertion_type: CLIENT_ASSERTION_TYPE,
|
|
@@ -1,72 +1,70 @@
|
|
|
1
|
-
import { base64 } from "@better-auth/utils/base64";
|
|
2
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
2
|
import type { AwaitableFunction } from "../types";
|
|
4
|
-
import type { ClientAssertionConfig } from "./client-assertion";
|
|
5
|
-
import { resolveAssertionParams } from "./client-assertion";
|
|
6
3
|
import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
|
|
4
|
+
import type {
|
|
5
|
+
TokenEndpointAuth,
|
|
6
|
+
TokenEndpointSecretAuthentication,
|
|
7
|
+
} from "./token-endpoint-auth";
|
|
8
|
+
import { applyTokenEndpointAuth } from "./token-endpoint-auth";
|
|
9
|
+
|
|
10
|
+
interface ClientCredentialsTokenRequestInput {
|
|
11
|
+
options: AwaitableFunction<ProviderOptions>;
|
|
12
|
+
scope?: string | undefined;
|
|
13
|
+
authentication?: TokenEndpointSecretAuthentication | undefined;
|
|
14
|
+
tokenEndpointAuth?: TokenEndpointAuth | undefined;
|
|
15
|
+
tokenEndpoint?: string | undefined;
|
|
16
|
+
resource?: (string | string[]) | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ClientCredentialsTokenRequestBaseInput {
|
|
20
|
+
options: ProviderOptions;
|
|
21
|
+
scope?: string | undefined;
|
|
22
|
+
resource?: (string | string[]) | undefined;
|
|
23
|
+
extraParams?: Record<string, string> | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ClientCredentialsTokenInput
|
|
27
|
+
extends ClientCredentialsTokenRequestInput {
|
|
28
|
+
tokenEndpoint: string;
|
|
29
|
+
scope: string;
|
|
30
|
+
}
|
|
7
31
|
|
|
8
32
|
export async function clientCredentialsTokenRequest({
|
|
9
33
|
options,
|
|
10
34
|
scope,
|
|
11
35
|
authentication,
|
|
12
|
-
|
|
36
|
+
tokenEndpointAuth,
|
|
13
37
|
tokenEndpoint,
|
|
14
38
|
resource,
|
|
15
|
-
}: {
|
|
16
|
-
options: AwaitableFunction<ProviderOptions>;
|
|
17
|
-
scope?: string | undefined;
|
|
18
|
-
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
19
|
-
clientAssertion?: ClientAssertionConfig | undefined;
|
|
20
|
-
/** Token endpoint URL. Used as the JWT `aud` claim when signing assertions. */
|
|
21
|
-
tokenEndpoint?: string | undefined;
|
|
22
|
-
resource?: (string | string[]) | undefined;
|
|
23
|
-
}) {
|
|
39
|
+
}: ClientCredentialsTokenRequestInput) {
|
|
24
40
|
options = typeof options === "function" ? await options() : options;
|
|
25
|
-
|
|
26
|
-
let extraParams: Record<string, string> | undefined;
|
|
27
|
-
if (authentication === "private_key_jwt") {
|
|
28
|
-
if (!clientAssertion) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
"private_key_jwt authentication requires a clientAssertion configuration",
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
34
|
-
? options.clientId[0]
|
|
35
|
-
: options.clientId;
|
|
36
|
-
extraParams = await resolveAssertionParams({
|
|
37
|
-
clientAssertion,
|
|
38
|
-
clientId: primaryClientId,
|
|
39
|
-
tokenEndpoint,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return createClientCredentialsTokenRequest({
|
|
41
|
+
const request = buildClientCredentialsTokenRequest({
|
|
44
42
|
options,
|
|
45
43
|
scope,
|
|
46
|
-
authentication,
|
|
47
44
|
resource,
|
|
48
|
-
extraParams,
|
|
49
45
|
});
|
|
46
|
+
|
|
47
|
+
await applyTokenEndpointAuth({
|
|
48
|
+
body: request.body,
|
|
49
|
+
headers: request.headers,
|
|
50
|
+
options,
|
|
51
|
+
tokenEndpoint: tokenEndpoint ?? "",
|
|
52
|
+
grantType: "client_credentials",
|
|
53
|
+
tokenEndpointAuth,
|
|
54
|
+
authentication,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return request;
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
* @deprecated use async'd clientCredentialsTokenRequest instead
|
|
54
|
-
*/
|
|
55
|
-
export function createClientCredentialsTokenRequest({
|
|
60
|
+
function buildClientCredentialsTokenRequest({
|
|
56
61
|
options,
|
|
57
62
|
scope,
|
|
58
|
-
authentication,
|
|
59
63
|
resource,
|
|
60
64
|
extraParams,
|
|
61
|
-
}: {
|
|
62
|
-
options: ProviderOptions;
|
|
63
|
-
scope?: string | undefined;
|
|
64
|
-
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
65
|
-
resource?: (string | string[]) | undefined;
|
|
66
|
-
extraParams?: Record<string, string> | undefined;
|
|
67
|
-
}) {
|
|
65
|
+
}: ClientCredentialsTokenRequestBaseInput) {
|
|
68
66
|
const body = new URLSearchParams();
|
|
69
|
-
const headers: Record<string,
|
|
67
|
+
const headers: Record<string, string> = {
|
|
70
68
|
"content-type": "application/x-www-form-urlencoded",
|
|
71
69
|
accept: "application/json",
|
|
72
70
|
};
|
|
@@ -82,21 +80,6 @@ export function createClientCredentialsTokenRequest({
|
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
|
-
const primaryClientId = Array.isArray(options.clientId)
|
|
86
|
-
? options.clientId[0]
|
|
87
|
-
: options.clientId;
|
|
88
|
-
if (authentication === "basic") {
|
|
89
|
-
const encodedCredentials = base64.encode(
|
|
90
|
-
`${primaryClientId}:${options.clientSecret ?? ""}`,
|
|
91
|
-
);
|
|
92
|
-
headers["authorization"] = `Basic ${encodedCredentials}`;
|
|
93
|
-
} else {
|
|
94
|
-
body.set("client_id", primaryClientId);
|
|
95
|
-
if (authentication !== "private_key_jwt" && options.clientSecret) {
|
|
96
|
-
body.set("client_secret", options.clientSecret);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
83
|
if (extraParams) {
|
|
101
84
|
for (const [key, value] of Object.entries(extraParams)) {
|
|
102
85
|
if (!body.has(key)) body.append(key, value);
|
|
@@ -114,21 +97,14 @@ export async function clientCredentialsToken({
|
|
|
114
97
|
tokenEndpoint,
|
|
115
98
|
scope,
|
|
116
99
|
authentication,
|
|
117
|
-
|
|
100
|
+
tokenEndpointAuth,
|
|
118
101
|
resource,
|
|
119
|
-
}: {
|
|
120
|
-
options: AwaitableFunction<ProviderOptions>;
|
|
121
|
-
tokenEndpoint: string;
|
|
122
|
-
scope: string;
|
|
123
|
-
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
124
|
-
clientAssertion?: ClientAssertionConfig | undefined;
|
|
125
|
-
resource?: (string | string[]) | undefined;
|
|
126
|
-
}): Promise<OAuth2Tokens> {
|
|
102
|
+
}: ClientCredentialsTokenInput): Promise<OAuth2Tokens> {
|
|
127
103
|
const { body, headers } = await clientCredentialsTokenRequest({
|
|
128
104
|
options,
|
|
129
105
|
scope,
|
|
130
106
|
authentication,
|
|
131
|
-
|
|
107
|
+
tokenEndpointAuth,
|
|
132
108
|
tokenEndpoint,
|
|
133
109
|
resource,
|
|
134
110
|
});
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import type { AwaitableFunction } from "../types";
|
|
2
2
|
import type { ProviderOptions } from "./index";
|
|
3
|
-
import { generateCodeChallenge } from "./utils";
|
|
3
|
+
import { generateCodeChallenge, getPrimaryClientId } from "./utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Query-parameter names that are populated by the framework as part of the
|
|
7
|
+
* authorization request and must not be overridden by caller-supplied
|
|
8
|
+
* `additionalParams`. Overriding `state`, PKCE, or `redirect_uri` would
|
|
9
|
+
* break the callback correlation and session pinning guarantees.
|
|
10
|
+
*/
|
|
11
|
+
export const RESERVED_AUTHORIZATION_PARAMS = [
|
|
12
|
+
"state",
|
|
13
|
+
"client_id",
|
|
14
|
+
"redirect_uri",
|
|
15
|
+
"response_type",
|
|
16
|
+
"code_challenge",
|
|
17
|
+
"code_challenge_method",
|
|
18
|
+
"scope",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export const RESERVED_AUTHORIZATION_PARAMS_SET: ReadonlySet<string> = new Set(
|
|
22
|
+
RESERVED_AUTHORIZATION_PARAMS,
|
|
23
|
+
);
|
|
4
24
|
|
|
5
25
|
export async function createAuthorizationURL({
|
|
6
26
|
id,
|
|
@@ -44,9 +64,10 @@ export async function createAuthorizationURL({
|
|
|
44
64
|
options = typeof options === "function" ? await options() : options;
|
|
45
65
|
const url = new URL(options.authorizationEndpoint || authorizationEndpoint);
|
|
46
66
|
url.searchParams.set("response_type", responseType || "code");
|
|
47
|
-
const primaryClientId =
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
const primaryClientId = getPrimaryClientId(options.clientId);
|
|
68
|
+
if (!primaryClientId) {
|
|
69
|
+
throw new Error("OAuth provider requires clientId");
|
|
70
|
+
}
|
|
50
71
|
url.searchParams.set("client_id", primaryClientId);
|
|
51
72
|
url.searchParams.set("state", state);
|
|
52
73
|
if (scopes) {
|
|
@@ -81,9 +102,10 @@ export async function createAuthorizationURL({
|
|
|
81
102
|
);
|
|
82
103
|
}
|
|
83
104
|
if (additionalParams) {
|
|
84
|
-
|
|
105
|
+
for (const [key, value] of Object.entries(additionalParams)) {
|
|
106
|
+
if (RESERVED_AUTHORIZATION_PARAMS_SET.has(key)) continue;
|
|
85
107
|
url.searchParams.set(key, value);
|
|
86
|
-
}
|
|
108
|
+
}
|
|
87
109
|
}
|
|
88
110
|
return url;
|
|
89
111
|
}
|
package/src/oauth2/index.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
|
+
export { additionalAuthorizationParamsSchema } from "./authorization-params";
|
|
2
|
+
export {
|
|
3
|
+
decodeBasicCredentials,
|
|
4
|
+
encodeBasicCredentials,
|
|
5
|
+
} from "./basic-credentials";
|
|
1
6
|
export type {
|
|
2
|
-
|
|
3
|
-
|
|
7
|
+
ClientAssertionContext,
|
|
8
|
+
ClientAssertionGetter,
|
|
9
|
+
ClientAssertionGrantType,
|
|
10
|
+
PrivateKeyJwtClientAssertionGetterOptions,
|
|
11
|
+
PrivateKeyJwtSigningAlgorithm,
|
|
4
12
|
} from "./client-assertion";
|
|
5
13
|
export {
|
|
6
|
-
ASSERTION_SIGNING_ALGORITHMS,
|
|
7
14
|
CLIENT_ASSERTION_TYPE,
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
createPrivateKeyJwtClientAssertionGetter,
|
|
16
|
+
PRIVATE_KEY_JWT_SIGNING_ALGORITHMS,
|
|
17
|
+
resolveClientAssertionParams,
|
|
18
|
+
signPrivateKeyJwtClientAssertion,
|
|
10
19
|
} from "./client-assertion";
|
|
11
20
|
export {
|
|
12
21
|
clientCredentialsToken,
|
|
13
22
|
clientCredentialsTokenRequest,
|
|
14
|
-
createClientCredentialsTokenRequest,
|
|
15
23
|
} from "./client-credentials-token";
|
|
16
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
createAuthorizationURL,
|
|
26
|
+
RESERVED_AUTHORIZATION_PARAMS,
|
|
27
|
+
RESERVED_AUTHORIZATION_PARAMS_SET,
|
|
28
|
+
} from "./create-authorization-url";
|
|
17
29
|
export type {
|
|
18
30
|
OAuth2Tokens,
|
|
19
31
|
OAuth2UserInfo,
|
|
@@ -21,18 +33,22 @@ export type {
|
|
|
21
33
|
ProviderOptions,
|
|
22
34
|
} from "./oauth-provider";
|
|
23
35
|
export {
|
|
24
|
-
createRefreshAccessTokenRequest,
|
|
25
36
|
refreshAccessToken,
|
|
26
37
|
refreshAccessTokenRequest,
|
|
27
38
|
} from "./refresh-access-token";
|
|
39
|
+
export type {
|
|
40
|
+
TokenEndpointAuth,
|
|
41
|
+
TokenEndpointAuthMethod,
|
|
42
|
+
TokenEndpointSecretAuthentication,
|
|
43
|
+
} from "./token-endpoint-auth";
|
|
28
44
|
export {
|
|
45
|
+
applyDefaultAccessTokenExpiry,
|
|
29
46
|
generateCodeChallenge,
|
|
30
47
|
getOAuth2Tokens,
|
|
31
48
|
getPrimaryClientId,
|
|
32
49
|
} from "./utils";
|
|
33
50
|
export {
|
|
34
51
|
authorizationCodeRequest,
|
|
35
|
-
createAuthorizationCodeRequest,
|
|
36
52
|
validateAuthorizationCode,
|
|
37
53
|
validateToken,
|
|
38
54
|
} from "./validate-authorization-code";
|
|
@@ -35,6 +35,13 @@ export interface OAuthProvider<
|
|
|
35
35
|
redirectURI: string;
|
|
36
36
|
display?: string | undefined;
|
|
37
37
|
loginHint?: string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Extra query parameters to append to the authorization URL.
|
|
40
|
+
* Providers forward these to the shared `createAuthorizationURL` helper,
|
|
41
|
+
* which drops any keys present in `RESERVED_AUTHORIZATION_PARAMS`
|
|
42
|
+
* before applying them.
|
|
43
|
+
*/
|
|
44
|
+
additionalParams?: Record<string, string> | undefined;
|
|
38
45
|
}) => Awaitable<URL>;
|
|
39
46
|
name: string;
|
|
40
47
|
validateAuthorizationCode: (data: {
|
|
@@ -94,6 +101,17 @@ export interface OAuthProvider<
|
|
|
94
101
|
* Disable sign up for new users.
|
|
95
102
|
*/
|
|
96
103
|
disableSignUp?: boolean | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Accept callbacks that arrive without a `state` parameter. When true,
|
|
106
|
+
* the shared OAuth callback handler restarts the flow server-side with
|
|
107
|
+
* fresh `state` and PKCE instead of rejecting the request. Intended for
|
|
108
|
+
* providers that initiate OAuth without RP-side flow kickoff (e.g.
|
|
109
|
+
* Clever). Leave unset for any provider that always initiates from the
|
|
110
|
+
* RP.
|
|
111
|
+
*
|
|
112
|
+
* @default false
|
|
113
|
+
*/
|
|
114
|
+
allowIdpInitiated?: boolean | undefined;
|
|
97
115
|
/**
|
|
98
116
|
* Options for the provider
|
|
99
117
|
*/
|
|
@@ -104,9 +122,10 @@ export type ProviderOptions<Profile extends Record<string, any> = any> = {
|
|
|
104
122
|
/**
|
|
105
123
|
* The client ID of your application.
|
|
106
124
|
*
|
|
107
|
-
*
|
|
125
|
+
* Some providers accept multiple platform client IDs. The first entry is the
|
|
126
|
+
* primary client ID used for token endpoint client authentication.
|
|
108
127
|
*/
|
|
109
|
-
clientId?:
|
|
128
|
+
clientId?: LiteralString | string[] | undefined;
|
|
110
129
|
/**
|
|
111
130
|
* The client secret of your application
|
|
112
131
|
*/
|