@better-auth/core 1.7.0-beta.0 → 1.7.0-beta.10
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/api/index.d.mts +44 -1
- package/dist/api/index.mjs +69 -4
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +91 -3
- package/dist/db/adapter/get-id-field.mjs +1 -1
- package/dist/db/adapter/index.d.mts +87 -3
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +2 -1
- package/dist/db/type.d.mts +17 -0
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +1 -0
- package/dist/error/codes.mjs +1 -0
- package/dist/error/index.d.mts +7 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/api.mjs +12 -0
- package/dist/instrumentation/noop.mjs +42 -0
- package/dist/instrumentation/pure.index.d.mts +7 -0
- package/dist/instrumentation/pure.index.mjs +7 -0
- package/dist/instrumentation/tracer.mjs +6 -3
- 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 +20 -31
- package/dist/oauth2/create-authorization-url.d.mts +11 -1
- package/dist/oauth2/create-authorization-url.mjs +27 -7
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +14 -9
- package/dist/oauth2/index.mjs +12 -8
- package/dist/oauth2/oauth-provider.d.mts +150 -10
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +38 -36
- package/dist/oauth2/reject-redirects.mjs +65 -0
- 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 +23 -1
- package/dist/oauth2/utils.mjs +48 -2
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +28 -34
- package/dist/oauth2/verify-id-token.d.mts +27 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +88 -15
- package/dist/oauth2/verify.mjs +203 -25
- package/dist/social-providers/apple.d.mts +16 -4
- package/dist/social-providers/apple.mjs +17 -24
- package/dist/social-providers/atlassian.d.mts +5 -1
- package/dist/social-providers/atlassian.mjs +6 -3
- package/dist/social-providers/cognito.d.mts +27 -3
- package/dist/social-providers/cognito.mjs +15 -25
- package/dist/social-providers/discord.d.mts +7 -3
- package/dist/social-providers/discord.mjs +16 -3
- package/dist/social-providers/dropbox.d.mts +5 -1
- package/dist/social-providers/dropbox.mjs +5 -4
- package/dist/social-providers/facebook.d.mts +24 -5
- package/dist/social-providers/facebook.mjs +54 -20
- package/dist/social-providers/figma.d.mts +5 -1
- package/dist/social-providers/figma.mjs +4 -3
- package/dist/social-providers/github.d.mts +6 -2
- package/dist/social-providers/github.mjs +5 -4
- package/dist/social-providers/gitlab.d.mts +5 -1
- package/dist/social-providers/gitlab.mjs +3 -2
- package/dist/social-providers/google.d.mts +53 -5
- package/dist/social-providers/google.mjs +69 -25
- package/dist/social-providers/huggingface.d.mts +5 -1
- package/dist/social-providers/huggingface.mjs +3 -2
- package/dist/social-providers/index.d.mts +221 -45
- package/dist/social-providers/index.mjs +2 -2
- package/dist/social-providers/kakao.d.mts +5 -1
- package/dist/social-providers/kakao.mjs +3 -2
- package/dist/social-providers/kick.d.mts +5 -1
- package/dist/social-providers/kick.mjs +3 -2
- package/dist/social-providers/line.d.mts +8 -2
- package/dist/social-providers/line.mjs +5 -6
- package/dist/social-providers/linear.d.mts +5 -1
- package/dist/social-providers/linear.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +7 -3
- package/dist/social-providers/linkedin.mjs +4 -3
- package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
- package/dist/social-providers/microsoft-entra-id.mjs +37 -28
- package/dist/social-providers/naver.d.mts +5 -1
- package/dist/social-providers/naver.mjs +3 -2
- package/dist/social-providers/notion.d.mts +5 -1
- package/dist/social-providers/notion.mjs +5 -2
- package/dist/social-providers/paybin.d.mts +5 -1
- package/dist/social-providers/paybin.mjs +4 -3
- package/dist/social-providers/paypal.d.mts +6 -2
- package/dist/social-providers/paypal.mjs +19 -14
- package/dist/social-providers/polar.d.mts +5 -1
- package/dist/social-providers/polar.mjs +3 -2
- package/dist/social-providers/railway.d.mts +5 -1
- package/dist/social-providers/railway.mjs +3 -2
- package/dist/social-providers/reddit.d.mts +5 -1
- package/dist/social-providers/reddit.mjs +7 -5
- package/dist/social-providers/roblox.d.mts +6 -2
- package/dist/social-providers/roblox.mjs +12 -2
- package/dist/social-providers/salesforce.d.mts +5 -1
- package/dist/social-providers/salesforce.mjs +4 -3
- package/dist/social-providers/slack.d.mts +6 -2
- package/dist/social-providers/slack.mjs +11 -8
- package/dist/social-providers/spotify.d.mts +5 -1
- package/dist/social-providers/spotify.mjs +3 -2
- package/dist/social-providers/tiktok.d.mts +5 -1
- package/dist/social-providers/tiktok.mjs +14 -2
- package/dist/social-providers/twitch.d.mts +5 -1
- package/dist/social-providers/twitch.mjs +3 -2
- package/dist/social-providers/twitter.d.mts +4 -1
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/vercel.d.mts +5 -1
- package/dist/social-providers/vercel.mjs +3 -2
- package/dist/social-providers/vk.d.mts +5 -1
- package/dist/social-providers/vk.mjs +3 -2
- package/dist/social-providers/wechat.d.mts +5 -1
- package/dist/social-providers/wechat.mjs +8 -2
- package/dist/social-providers/zoom.d.mts +5 -1
- package/dist/social-providers/zoom.mjs +10 -17
- package/dist/types/context.d.mts +55 -6
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +182 -12
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/async.d.mts +22 -0
- package/dist/utils/async.mjs +32 -0
- package/dist/utils/host.d.mts +147 -0
- package/dist/utils/host.mjs +298 -0
- package/dist/utils/ip.d.mts +28 -5
- package/dist/utils/ip.mjs +118 -4
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- 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 +34 -4
- package/package.json +26 -16
- package/src/api/index.ts +121 -5
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +193 -7
- package/src/db/adapter/get-id-field.ts +2 -2
- package/src/db/adapter/index.ts +85 -2
- package/src/db/adapter/types.ts +2 -0
- package/src/db/get-tables.ts +9 -1
- package/src/db/schema/account.ts +4 -1
- package/src/db/schema/user.ts +3 -0
- package/src/db/type.ts +17 -0
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +1 -0
- package/src/error/index.ts +9 -0
- package/src/instrumentation/api.ts +17 -0
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/instrumentation/tracer.ts +8 -3
- 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 +50 -74
- package/src/oauth2/create-authorization-url.ts +33 -7
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +81 -11
- package/src/oauth2/oauth-provider.ts +159 -11
- package/src/oauth2/refresh-access-token.ts +82 -83
- package/src/oauth2/reject-redirects.ts +75 -0
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +72 -5
- package/src/oauth2/validate-authorization-code.ts +69 -89
- package/src/oauth2/verify-id-token.ts +115 -0
- package/src/oauth2/verify.ts +409 -78
- package/src/social-providers/apple.ts +30 -40
- package/src/social-providers/atlassian.ts +8 -1
- package/src/social-providers/cognito.ts +36 -39
- package/src/social-providers/discord.ts +22 -18
- package/src/social-providers/dropbox.ts +7 -5
- package/src/social-providers/facebook.ts +108 -52
- package/src/social-providers/figma.ts +8 -1
- package/src/social-providers/github.ts +5 -3
- package/src/social-providers/gitlab.ts +2 -0
- package/src/social-providers/google.ts +141 -39
- 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 +25 -27
- package/src/social-providers/linear.ts +8 -1
- package/src/social-providers/linkedin.ts +5 -3
- package/src/social-providers/microsoft-entra-id.ts +100 -55
- 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 +28 -17
- package/src/social-providers/polar.ts +8 -1
- package/src/social-providers/railway.ts +8 -1
- package/src/social-providers/reddit.ts +9 -4
- 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 +17 -2
- package/src/social-providers/zoom.ts +15 -19
- package/src/types/context.ts +57 -5
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +204 -14
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/async.ts +53 -0
- package/src/utils/host.ts +425 -0
- package/src/utils/ip.ts +197 -13
- package/src/utils/is-api-error.ts +10 -0
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +38 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID } from "./attributes.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/instrumentation/pure.index.d.ts
|
|
4
|
+
declare function withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: () => T): T;
|
|
5
|
+
declare function withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: () => Promise<T>): Promise<T>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID } from "./attributes.mjs";
|
|
2
|
+
//#region src/instrumentation/pure.index.ts
|
|
3
|
+
function withSpan(_name, _attributes, fn) {
|
|
4
|
+
return fn();
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ATTR_HTTP_RESPONSE_STATUS_CODE } from "./attributes.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { getOpenTelemetryAPI } from "./api.mjs";
|
|
3
3
|
//#region src/instrumentation/tracer.ts
|
|
4
|
-
const
|
|
4
|
+
const INSTRUMENTATION_SCOPE = "better-auth";
|
|
5
|
+
const INSTRUMENTATION_VERSION = "1.7.0-beta.10";
|
|
5
6
|
/**
|
|
6
7
|
* Better-auth uses `throw ctx.redirect(url)` for flow control (e.g. OAuth
|
|
7
8
|
* callbacks). These are APIErrors with 3xx status codes and should not be
|
|
@@ -15,6 +16,7 @@ function isRedirectError(err) {
|
|
|
15
16
|
return false;
|
|
16
17
|
}
|
|
17
18
|
function endSpanWithError(span, err) {
|
|
19
|
+
const { SpanStatusCode } = getOpenTelemetryAPI();
|
|
18
20
|
if (isRedirectError(err)) {
|
|
19
21
|
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, err.statusCode);
|
|
20
22
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
@@ -28,7 +30,8 @@ function endSpanWithError(span, err) {
|
|
|
28
30
|
span.end();
|
|
29
31
|
}
|
|
30
32
|
function withSpan(name, attributes, fn) {
|
|
31
|
-
|
|
33
|
+
const { trace } = getOpenTelemetryAPI();
|
|
34
|
+
return trace.getTracer(INSTRUMENTATION_SCOPE, INSTRUMENTATION_VERSION).startActiveSpan(name, { attributes }, (span) => {
|
|
32
35
|
try {
|
|
33
36
|
const result = fn();
|
|
34
37
|
if (result instanceof Promise) return result.then((value) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/oauth2/authorization-params.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for the `additionalParams` field on social sign-in and
|
|
6
|
+
* account-linking request bodies. Rejects any key reserved by the
|
|
7
|
+
* authorization-URL builder (see `RESERVED_AUTHORIZATION_PARAMS`), so
|
|
8
|
+
* a caller cannot overwrite `state`, PKCE, `redirect_uri`, etc.
|
|
9
|
+
*/
|
|
10
|
+
declare const additionalAuthorizationParamsSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { additionalAuthorizationParamsSchema };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET } from "./create-authorization-url.mjs";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
//#region src/oauth2/authorization-params.ts
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for the `additionalParams` field on social sign-in and
|
|
6
|
+
* account-linking request bodies. Rejects any key reserved by the
|
|
7
|
+
* authorization-URL builder (see `RESERVED_AUTHORIZATION_PARAMS`), so
|
|
8
|
+
* a caller cannot overwrite `state`, PKCE, `redirect_uri`, etc.
|
|
9
|
+
*/
|
|
10
|
+
const additionalAuthorizationParamsSchema = z.record(z.string(), z.string()).refine((value) => !Object.keys(value).some((key) => RESERVED_AUTHORIZATION_PARAMS_SET.has(key)), { message: `additionalParams cannot include reserved OAuth parameters: ${RESERVED_AUTHORIZATION_PARAMS.join(", ")}` }).meta({ description: "Extra query parameters to append to the provider authorization URL (e.g. Cognito identity_provider, Google hd)." }).optional();
|
|
11
|
+
//#endregion
|
|
12
|
+
export { additionalAuthorizationParamsSchema };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/oauth2/basic-credentials.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Encodes an OAuth client id and secret as an HTTP Basic credential string.
|
|
4
|
+
*
|
|
5
|
+
* Follows RFC 6749 §2.3.1: both values are `application/x-www-form-urlencoded`
|
|
6
|
+
* prior to base64 encoding. The returned string is the full value of the
|
|
7
|
+
* `Authorization` header, including the `Basic ` prefix.
|
|
8
|
+
*/
|
|
9
|
+
declare function encodeBasicCredentials(clientId: string, clientSecret: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Decodes an `Authorization: Basic …` header value into its OAuth client id
|
|
12
|
+
* and secret.
|
|
13
|
+
*
|
|
14
|
+
* Scheme matching is case-insensitive and tolerates one or more spaces
|
|
15
|
+
* between the scheme and credentials per RFC 7235 §2.1. The base64 payload
|
|
16
|
+
* is split on the first `:` only, so secrets containing colons round-trip
|
|
17
|
+
* correctly. Each half is form-url-decoded per RFC 6749 §2.3.1, accepting
|
|
18
|
+
* both `+` and `%20` as space. Per the URL Living Standard, invalid
|
|
19
|
+
* percent-escapes pass through as-is; downstream client lookup will fail
|
|
20
|
+
* with `invalid_client` for malformed credentials.
|
|
21
|
+
*
|
|
22
|
+
* Throws when the header is not a Basic credential, when the base64 payload
|
|
23
|
+
* contains no `:`, or when either half is empty.
|
|
24
|
+
*/
|
|
25
|
+
declare function decodeBasicCredentials(authorization: string): {
|
|
26
|
+
clientId: string;
|
|
27
|
+
clientSecret: string;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { decodeBasicCredentials, encodeBasicCredentials };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
2
|
+
//#region src/oauth2/basic-credentials.ts
|
|
3
|
+
const BASIC_AUTHORIZATION_PATTERN = /^Basic +(.*)$/i;
|
|
4
|
+
/**
|
|
5
|
+
* Encodes a value using `application/x-www-form-urlencoded` per the URL
|
|
6
|
+
* Living Standard. Differs from `encodeURIComponent` in two ways: it escapes
|
|
7
|
+
* `!`, `'`, `(`, and `)`, and it represents space as `+` rather than `%20`.
|
|
8
|
+
* `*` is left unescaped, matching the URL Standard's percent-encode set.
|
|
9
|
+
*/
|
|
10
|
+
function formUrlEncode(value) {
|
|
11
|
+
return new URLSearchParams({ v: value }).toString().slice(2);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Inverse of `formUrlEncode`: decodes a single `application/x-www-form-urlencoded`
|
|
15
|
+
* value, handling both `+` and `%20` as space.
|
|
16
|
+
*/
|
|
17
|
+
function formUrlDecode(value) {
|
|
18
|
+
const decoded = new URLSearchParams(`v=${value}`).get("v");
|
|
19
|
+
if (decoded === null) throw new Error("form-url-encoded value could not be decoded");
|
|
20
|
+
return decoded;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encodes an OAuth client id and secret as an HTTP Basic credential string.
|
|
24
|
+
*
|
|
25
|
+
* Follows RFC 6749 §2.3.1: both values are `application/x-www-form-urlencoded`
|
|
26
|
+
* prior to base64 encoding. The returned string is the full value of the
|
|
27
|
+
* `Authorization` header, including the `Basic ` prefix.
|
|
28
|
+
*/
|
|
29
|
+
function encodeBasicCredentials(clientId, clientSecret) {
|
|
30
|
+
const payload = `${formUrlEncode(clientId)}:${formUrlEncode(clientSecret)}`;
|
|
31
|
+
return `Basic ${base64.encode(payload)}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Decodes an `Authorization: Basic …` header value into its OAuth client id
|
|
35
|
+
* and secret.
|
|
36
|
+
*
|
|
37
|
+
* Scheme matching is case-insensitive and tolerates one or more spaces
|
|
38
|
+
* between the scheme and credentials per RFC 7235 §2.1. The base64 payload
|
|
39
|
+
* is split on the first `:` only, so secrets containing colons round-trip
|
|
40
|
+
* correctly. Each half is form-url-decoded per RFC 6749 §2.3.1, accepting
|
|
41
|
+
* both `+` and `%20` as space. Per the URL Living Standard, invalid
|
|
42
|
+
* percent-escapes pass through as-is; downstream client lookup will fail
|
|
43
|
+
* with `invalid_client` for malformed credentials.
|
|
44
|
+
*
|
|
45
|
+
* Throws when the header is not a Basic credential, when the base64 payload
|
|
46
|
+
* contains no `:`, or when either half is empty.
|
|
47
|
+
*/
|
|
48
|
+
function decodeBasicCredentials(authorization) {
|
|
49
|
+
const match = authorization.match(BASIC_AUTHORIZATION_PATTERN);
|
|
50
|
+
if (!match) throw new Error("Authorization header is not a Basic credential");
|
|
51
|
+
const encoded = match[1] ?? "";
|
|
52
|
+
const decoded = new TextDecoder().decode(base64.decode(encoded));
|
|
53
|
+
const separatorIndex = decoded.indexOf(":");
|
|
54
|
+
if (separatorIndex === -1) throw new Error("Basic credential is missing the client id/secret separator");
|
|
55
|
+
const rawClientId = decoded.slice(0, separatorIndex);
|
|
56
|
+
const rawClientSecret = decoded.slice(separatorIndex + 1);
|
|
57
|
+
if (!rawClientId || !rawClientSecret) throw new Error("Basic credential client id and secret must both be non-empty");
|
|
58
|
+
return {
|
|
59
|
+
clientId: formUrlDecode(rawClientId),
|
|
60
|
+
clientSecret: formUrlDecode(rawClientSecret)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { decodeBasicCredentials, encodeBasicCredentials };
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { Awaitable } from "../types/helper.mjs";
|
|
1
2
|
//#region src/oauth2/client-assertion.d.ts
|
|
2
3
|
/** Asymmetric signing algorithms compatible with private_key_jwt (RFC 7523). */
|
|
3
|
-
declare const
|
|
4
|
-
type
|
|
4
|
+
declare const PRIVATE_KEY_JWT_SIGNING_ALGORITHMS: readonly ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
5
|
+
type PrivateKeyJwtSigningAlgorithm = (typeof PRIVATE_KEY_JWT_SIGNING_ALGORITHMS)[number];
|
|
5
6
|
declare const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
type ClientAssertionGrantType = "authorization_code" | "refresh_token" | "client_credentials";
|
|
8
|
+
interface ClientAssertionContext {
|
|
9
|
+
clientId: string;
|
|
10
|
+
tokenEndpoint: string;
|
|
11
|
+
grantType: ClientAssertionGrantType;
|
|
12
|
+
}
|
|
13
|
+
type ClientAssertionGetter = (context: ClientAssertionContext) => Awaitable<string>;
|
|
14
|
+
interface PrivateKeyJwtClientAssertionGetterOptions {
|
|
9
15
|
/** Private key in JWK format for signing. */
|
|
10
16
|
privateKeyJwk?: JsonWebKey;
|
|
11
17
|
/** Private key in PKCS#8 PEM format for signing. */
|
|
@@ -13,19 +19,23 @@ interface ClientAssertionConfig {
|
|
|
13
19
|
/** Key ID to include in the JWT header. */
|
|
14
20
|
kid?: string;
|
|
15
21
|
/** Asymmetric signing algorithm. Symmetric algorithms (HS256) and "none" are not allowed. @default "RS256" */
|
|
16
|
-
algorithm?:
|
|
17
|
-
/** Token endpoint URL (used as the JWT `aud` claim). */
|
|
18
|
-
tokenEndpoint?: string;
|
|
22
|
+
algorithm?: PrivateKeyJwtSigningAlgorithm;
|
|
19
23
|
/** Assertion lifetime in seconds. @default 120 */
|
|
20
24
|
expiresIn?: number;
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
27
|
* Signs an RFC 7523 client assertion JWT for `private_key_jwt` authentication.
|
|
24
28
|
*
|
|
25
|
-
* The JWT contains
|
|
26
|
-
*
|
|
29
|
+
* The JWT contains these claims:
|
|
30
|
+
*
|
|
31
|
+
* - iss=clientId
|
|
32
|
+
* - sub=clientId
|
|
33
|
+
* - aud=tokenEndpoint
|
|
34
|
+
* - exp=now + 120s
|
|
35
|
+
* - jti=unique
|
|
36
|
+
* - iat=now
|
|
27
37
|
*/
|
|
28
|
-
declare function
|
|
38
|
+
declare function signPrivateKeyJwtClientAssertion({
|
|
29
39
|
clientId,
|
|
30
40
|
tokenEndpoint,
|
|
31
41
|
privateKeyJwk,
|
|
@@ -39,21 +49,27 @@ declare function signClientAssertion({
|
|
|
39
49
|
privateKeyJwk?: JsonWebKey;
|
|
40
50
|
privateKeyPem?: string;
|
|
41
51
|
kid?: string;
|
|
42
|
-
algorithm?:
|
|
52
|
+
algorithm?: PrivateKeyJwtSigningAlgorithm;
|
|
43
53
|
expiresIn?: number;
|
|
44
54
|
}): Promise<string>;
|
|
45
55
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
56
|
+
* Creates a client assertion getter for `private_key_jwt` authentication.
|
|
57
|
+
*
|
|
58
|
+
* Validates options eagerly (key material, supported algorithm, JWK alg
|
|
59
|
+
* agreement) so misconfiguration surfaces at construction rather than on the
|
|
60
|
+
* first token request. The returned function signs a fresh RFC 7523 JWT
|
|
61
|
+
* assertion for every token endpoint request.
|
|
48
62
|
*/
|
|
49
|
-
declare function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
declare function createPrivateKeyJwtClientAssertionGetter(options: PrivateKeyJwtClientAssertionGetterOptions): ClientAssertionGetter;
|
|
64
|
+
/**
|
|
65
|
+
* Resolves a client assertion getter into `client_assertion` + `client_assertion_type` params for injection into a token request body.
|
|
66
|
+
*/
|
|
67
|
+
declare function resolveClientAssertionParams({
|
|
68
|
+
getClientAssertion,
|
|
69
|
+
context
|
|
53
70
|
}: {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
tokenEndpoint?: string;
|
|
71
|
+
getClientAssertion: ClientAssertionGetter;
|
|
72
|
+
context: ClientAssertionContext;
|
|
57
73
|
}): Promise<Record<string, string>>;
|
|
58
74
|
//#endregion
|
|
59
|
-
export {
|
|
75
|
+
export { CLIENT_ASSERTION_TYPE, ClientAssertionContext, ClientAssertionGetter, ClientAssertionGrantType, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, PrivateKeyJwtClientAssertionGetterOptions, PrivateKeyJwtSigningAlgorithm, createPrivateKeyJwtClientAssertionGetter, resolveClientAssertionParams, signPrivateKeyJwtClientAssertion };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SignJWT, importJWK, importPKCS8 } from "jose";
|
|
2
2
|
//#region src/oauth2/client-assertion.ts
|
|
3
3
|
/** Asymmetric signing algorithms compatible with private_key_jwt (RFC 7523). */
|
|
4
|
-
const
|
|
4
|
+
const PRIVATE_KEY_JWT_SIGNING_ALGORITHMS = [
|
|
5
5
|
"RS256",
|
|
6
6
|
"RS384",
|
|
7
7
|
"RS512",
|
|
@@ -13,20 +13,46 @@ const ASSERTION_SIGNING_ALGORITHMS = [
|
|
|
13
13
|
"ES512",
|
|
14
14
|
"EdDSA"
|
|
15
15
|
];
|
|
16
|
+
function assertSupportedPrivateKeyJwtAlgorithm(candidate) {
|
|
17
|
+
if (!PRIVATE_KEY_JWT_SIGNING_ALGORITHMS.includes(candidate)) throw new Error(`Unsupported private_key_jwt signing algorithm: ${candidate}. Use one of ${PRIVATE_KEY_JWT_SIGNING_ALGORITHMS.join(", ")}.`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validates `private_key_jwt` options eagerly and returns the algorithm to
|
|
21
|
+
* use for signing.
|
|
22
|
+
*
|
|
23
|
+
* Asserts that key material is configured, that any explicit `algorithm` is
|
|
24
|
+
* supported, that any JWK-embedded `alg` is supported, and that the two
|
|
25
|
+
* agree when both are set.
|
|
26
|
+
*/
|
|
27
|
+
function resolveValidPrivateKeyJwtOptions(options) {
|
|
28
|
+
if (!options.privateKeyJwk && !options.privateKeyPem) throw new Error("private_key_jwt requires either privateKeyJwk or privateKeyPem");
|
|
29
|
+
if (options.algorithm) assertSupportedPrivateKeyJwtAlgorithm(options.algorithm);
|
|
30
|
+
const jwkAlg = options.privateKeyJwk?.alg;
|
|
31
|
+
if (typeof jwkAlg === "string") assertSupportedPrivateKeyJwtAlgorithm(jwkAlg);
|
|
32
|
+
if (options.algorithm && typeof jwkAlg === "string" && options.algorithm !== jwkAlg) throw new Error(`JWK alg "${jwkAlg}" does not match configured algorithm "${options.algorithm}". Remove the JWK alg field, or pass an algorithm that matches the JWK.`);
|
|
33
|
+
return options.algorithm ?? (typeof jwkAlg === "string" ? jwkAlg : "RS256");
|
|
34
|
+
}
|
|
16
35
|
const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
17
36
|
/**
|
|
18
37
|
* Signs an RFC 7523 client assertion JWT for `private_key_jwt` authentication.
|
|
19
38
|
*
|
|
20
|
-
* The JWT contains
|
|
21
|
-
*
|
|
39
|
+
* The JWT contains these claims:
|
|
40
|
+
*
|
|
41
|
+
* - iss=clientId
|
|
42
|
+
* - sub=clientId
|
|
43
|
+
* - aud=tokenEndpoint
|
|
44
|
+
* - exp=now + 120s
|
|
45
|
+
* - jti=unique
|
|
46
|
+
* - iat=now
|
|
22
47
|
*/
|
|
23
|
-
async function
|
|
48
|
+
async function signPrivateKeyJwtClientAssertion({ clientId, tokenEndpoint, privateKeyJwk, privateKeyPem, kid, algorithm, expiresIn = 120 }) {
|
|
49
|
+
const resolvedAlg = resolveValidPrivateKeyJwtOptions({
|
|
50
|
+
privateKeyJwk,
|
|
51
|
+
privateKeyPem,
|
|
52
|
+
algorithm
|
|
53
|
+
});
|
|
24
54
|
const resolvedKid = kid ?? privateKeyJwk?.kid;
|
|
25
|
-
const
|
|
26
|
-
let key;
|
|
27
|
-
if (privateKeyJwk) key = await importJWK(privateKeyJwk, resolvedAlg);
|
|
28
|
-
else if (privateKeyPem) key = await importPKCS8(privateKeyPem, resolvedAlg);
|
|
29
|
-
else throw new Error("private_key_jwt requires either privateKeyJwk or privateKeyPem");
|
|
55
|
+
const key = privateKeyJwk ? await importJWK(privateKeyJwk, resolvedAlg) : await importPKCS8(privateKeyPem, resolvedAlg);
|
|
30
56
|
const now = Math.floor(Date.now() / 1e3);
|
|
31
57
|
const jti = crypto.randomUUID();
|
|
32
58
|
const header = {
|
|
@@ -37,28 +63,37 @@ async function signClientAssertion({ clientId, tokenEndpoint, privateKeyJwk, pri
|
|
|
37
63
|
return new SignJWT({}).setProtectedHeader(header).setIssuer(clientId).setSubject(clientId).setAudience(tokenEndpoint).setIssuedAt(now).setExpirationTime(now + expiresIn).setJti(jti).sign(key);
|
|
38
64
|
}
|
|
39
65
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
66
|
+
* Creates a client assertion getter for `private_key_jwt` authentication.
|
|
67
|
+
*
|
|
68
|
+
* Validates options eagerly (key material, supported algorithm, JWK alg
|
|
69
|
+
* agreement) so misconfiguration surfaces at construction rather than on the
|
|
70
|
+
* first token request. The returned function signs a fresh RFC 7523 JWT
|
|
71
|
+
* assertion for every token endpoint request.
|
|
72
|
+
*/
|
|
73
|
+
function createPrivateKeyJwtClientAssertionGetter(options) {
|
|
74
|
+
resolveValidPrivateKeyJwtOptions({
|
|
75
|
+
privateKeyJwk: options.privateKeyJwk,
|
|
76
|
+
privateKeyPem: options.privateKeyPem,
|
|
77
|
+
algorithm: options.algorithm
|
|
78
|
+
});
|
|
79
|
+
return ({ clientId, tokenEndpoint }) => signPrivateKeyJwtClientAssertion({
|
|
80
|
+
clientId,
|
|
81
|
+
tokenEndpoint,
|
|
82
|
+
privateKeyJwk: options.privateKeyJwk,
|
|
83
|
+
privateKeyPem: options.privateKeyPem,
|
|
84
|
+
kid: options.kid,
|
|
85
|
+
algorithm: options.algorithm,
|
|
86
|
+
expiresIn: options.expiresIn
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves a client assertion getter into `client_assertion` + `client_assertion_type` params for injection into a token request body.
|
|
42
91
|
*/
|
|
43
|
-
async function
|
|
44
|
-
let assertion = clientAssertion.assertion;
|
|
45
|
-
if (!assertion) {
|
|
46
|
-
const audEndpoint = tokenEndpoint ?? clientAssertion.tokenEndpoint;
|
|
47
|
-
if (!audEndpoint) throw new Error("private_key_jwt requires a tokenEndpoint for the JWT audience claim");
|
|
48
|
-
assertion = await signClientAssertion({
|
|
49
|
-
clientId,
|
|
50
|
-
tokenEndpoint: audEndpoint,
|
|
51
|
-
privateKeyJwk: clientAssertion.privateKeyJwk,
|
|
52
|
-
privateKeyPem: clientAssertion.privateKeyPem,
|
|
53
|
-
kid: clientAssertion.kid,
|
|
54
|
-
algorithm: clientAssertion.algorithm,
|
|
55
|
-
expiresIn: clientAssertion.expiresIn
|
|
56
|
-
});
|
|
57
|
-
}
|
|
92
|
+
async function resolveClientAssertionParams({ getClientAssertion, context }) {
|
|
58
93
|
return {
|
|
59
|
-
client_assertion:
|
|
94
|
+
client_assertion: await getClientAssertion(context),
|
|
60
95
|
client_assertion_type: CLIENT_ASSERTION_TYPE
|
|
61
96
|
};
|
|
62
97
|
}
|
|
63
98
|
//#endregion
|
|
64
|
-
export {
|
|
99
|
+
export { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, createPrivateKeyJwtClientAssertionGetter, resolveClientAssertionParams, signPrivateKeyJwtClientAssertion };
|
|
@@ -1,59 +1,38 @@
|
|
|
1
|
-
import { ClientAssertionConfig } from "./client-assertion.mjs";
|
|
2
1
|
import { AwaitableFunction } from "../types/helper.mjs";
|
|
3
2
|
import { OAuth2Tokens, ProviderOptions } from "./oauth-provider.mjs";
|
|
3
|
+
import { TokenEndpointAuth, TokenEndpointSecretAuthentication } from "./token-endpoint-auth.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/oauth2/client-credentials-token.d.ts
|
|
6
|
-
|
|
7
|
-
options,
|
|
8
|
-
scope,
|
|
9
|
-
authentication,
|
|
10
|
-
clientAssertion,
|
|
11
|
-
tokenEndpoint,
|
|
12
|
-
resource
|
|
13
|
-
}: {
|
|
6
|
+
interface ClientCredentialsTokenRequestInput {
|
|
14
7
|
options: AwaitableFunction<ProviderOptions>;
|
|
15
8
|
scope?: string | undefined;
|
|
16
|
-
authentication?:
|
|
17
|
-
|
|
9
|
+
authentication?: TokenEndpointSecretAuthentication | undefined;
|
|
10
|
+
tokenEndpointAuth?: TokenEndpointAuth | undefined;
|
|
18
11
|
tokenEndpoint?: string | undefined;
|
|
19
12
|
resource?: (string | string[]) | undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
declare function createClientCredentialsTokenRequest({
|
|
13
|
+
}
|
|
14
|
+
interface ClientCredentialsTokenInput extends ClientCredentialsTokenRequestInput {
|
|
15
|
+
tokenEndpoint: string;
|
|
16
|
+
scope: string;
|
|
17
|
+
}
|
|
18
|
+
declare function clientCredentialsTokenRequest({
|
|
28
19
|
options,
|
|
29
20
|
scope,
|
|
30
21
|
authentication,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
scope?: string | undefined;
|
|
36
|
-
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
37
|
-
resource?: (string | string[]) | undefined;
|
|
38
|
-
extraParams?: Record<string, string> | undefined;
|
|
39
|
-
}): {
|
|
22
|
+
tokenEndpointAuth,
|
|
23
|
+
tokenEndpoint,
|
|
24
|
+
resource
|
|
25
|
+
}: ClientCredentialsTokenRequestInput): Promise<{
|
|
40
26
|
body: URLSearchParams;
|
|
41
|
-
headers: Record<string,
|
|
42
|
-
}
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
}>;
|
|
43
29
|
declare function clientCredentialsToken({
|
|
44
30
|
options,
|
|
45
31
|
tokenEndpoint,
|
|
46
32
|
scope,
|
|
47
33
|
authentication,
|
|
48
|
-
|
|
34
|
+
tokenEndpointAuth,
|
|
49
35
|
resource
|
|
50
|
-
}:
|
|
51
|
-
options: AwaitableFunction<ProviderOptions>;
|
|
52
|
-
tokenEndpoint: string;
|
|
53
|
-
scope: string;
|
|
54
|
-
authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
|
|
55
|
-
clientAssertion?: ClientAssertionConfig | undefined;
|
|
56
|
-
resource?: (string | string[]) | undefined;
|
|
57
|
-
}): Promise<OAuth2Tokens>;
|
|
36
|
+
}: ClientCredentialsTokenInput): Promise<OAuth2Tokens>;
|
|
58
37
|
//#endregion
|
|
59
|
-
export { clientCredentialsToken, clientCredentialsTokenRequest
|
|
38
|
+
export { clientCredentialsToken, clientCredentialsTokenRequest };
|
|
@@ -1,30 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
1
|
+
import { fetchRefusingRedirects } from "./reject-redirects.mjs";
|
|
2
|
+
import { applyTokenEndpointAuth } from "./token-endpoint-auth.mjs";
|
|
4
3
|
//#region src/oauth2/client-credentials-token.ts
|
|
5
|
-
async function clientCredentialsTokenRequest({ options, scope, authentication,
|
|
4
|
+
async function clientCredentialsTokenRequest({ options, scope, authentication, tokenEndpointAuth, tokenEndpoint, resource }) {
|
|
6
5
|
options = typeof options === "function" ? await options() : options;
|
|
7
|
-
|
|
8
|
-
if (authentication === "private_key_jwt") {
|
|
9
|
-
if (!clientAssertion) throw new Error("private_key_jwt authentication requires a clientAssertion configuration");
|
|
10
|
-
extraParams = await resolveAssertionParams({
|
|
11
|
-
clientAssertion,
|
|
12
|
-
clientId: Array.isArray(options.clientId) ? options.clientId[0] : options.clientId,
|
|
13
|
-
tokenEndpoint
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return createClientCredentialsTokenRequest({
|
|
6
|
+
const request = buildClientCredentialsTokenRequest({
|
|
17
7
|
options,
|
|
18
8
|
scope,
|
|
19
|
-
|
|
20
|
-
resource,
|
|
21
|
-
extraParams
|
|
9
|
+
resource
|
|
22
10
|
});
|
|
11
|
+
await applyTokenEndpointAuth({
|
|
12
|
+
body: request.body,
|
|
13
|
+
headers: request.headers,
|
|
14
|
+
options,
|
|
15
|
+
tokenEndpoint: tokenEndpoint ?? "",
|
|
16
|
+
grantType: "client_credentials",
|
|
17
|
+
tokenEndpointAuth,
|
|
18
|
+
authentication
|
|
19
|
+
});
|
|
20
|
+
return request;
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
* @deprecated use async'd clientCredentialsTokenRequest instead
|
|
26
|
-
*/
|
|
27
|
-
function createClientCredentialsTokenRequest({ options, scope, authentication, resource, extraParams }) {
|
|
22
|
+
function buildClientCredentialsTokenRequest({ options, scope, resource, extraParams }) {
|
|
28
23
|
const body = new URLSearchParams();
|
|
29
24
|
const headers = {
|
|
30
25
|
"content-type": "application/x-www-form-urlencoded",
|
|
@@ -34,12 +29,6 @@ function createClientCredentialsTokenRequest({ options, scope, authentication, r
|
|
|
34
29
|
scope && body.set("scope", scope);
|
|
35
30
|
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
36
31
|
else for (const _resource of resource) body.append("resource", _resource);
|
|
37
|
-
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
38
|
-
if (authentication === "basic") headers["authorization"] = `Basic ${base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`)}`;
|
|
39
|
-
else {
|
|
40
|
-
body.set("client_id", primaryClientId);
|
|
41
|
-
if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
42
|
-
}
|
|
43
32
|
if (extraParams) {
|
|
44
33
|
for (const [key, value] of Object.entries(extraParams)) if (!body.has(key)) body.append(key, value);
|
|
45
34
|
}
|
|
@@ -48,16 +37,16 @@ function createClientCredentialsTokenRequest({ options, scope, authentication, r
|
|
|
48
37
|
headers
|
|
49
38
|
};
|
|
50
39
|
}
|
|
51
|
-
async function clientCredentialsToken({ options, tokenEndpoint, scope, authentication,
|
|
40
|
+
async function clientCredentialsToken({ options, tokenEndpoint, scope, authentication, tokenEndpointAuth, resource }) {
|
|
52
41
|
const { body, headers } = await clientCredentialsTokenRequest({
|
|
53
42
|
options,
|
|
54
43
|
scope,
|
|
55
44
|
authentication,
|
|
56
|
-
|
|
45
|
+
tokenEndpointAuth,
|
|
57
46
|
tokenEndpoint,
|
|
58
47
|
resource
|
|
59
48
|
});
|
|
60
|
-
const { data, error } = await
|
|
49
|
+
const { data, error } = await fetchRefusingRedirects(tokenEndpoint, {
|
|
61
50
|
method: "POST",
|
|
62
51
|
body,
|
|
63
52
|
headers
|
|
@@ -75,4 +64,4 @@ async function clientCredentialsToken({ options, tokenEndpoint, scope, authentic
|
|
|
75
64
|
return tokens;
|
|
76
65
|
}
|
|
77
66
|
//#endregion
|
|
78
|
-
export { clientCredentialsToken, clientCredentialsTokenRequest
|
|
67
|
+
export { clientCredentialsToken, clientCredentialsTokenRequest };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { AwaitableFunction } from "../types/helper.mjs";
|
|
2
2
|
import { ProviderOptions } from "./oauth-provider.mjs";
|
|
3
3
|
//#region src/oauth2/create-authorization-url.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Query-parameter names that are populated by the framework as part of the
|
|
6
|
+
* authorization request and must not be overridden by caller-supplied
|
|
7
|
+
* `additionalParams`. Overriding `state`, PKCE, or `redirect_uri` would
|
|
8
|
+
* break the callback correlation and session pinning guarantees.
|
|
9
|
+
*/
|
|
10
|
+
declare const RESERVED_AUTHORIZATION_PARAMS: readonly ["state", "client_id", "redirect_uri", "response_type", "code_challenge", "code_challenge_method", "nonce", "scope"];
|
|
11
|
+
declare const RESERVED_AUTHORIZATION_PARAMS_SET: ReadonlySet<string>;
|
|
4
12
|
declare function createAuthorizationURL({
|
|
5
13
|
id,
|
|
6
14
|
options,
|
|
@@ -16,6 +24,7 @@ declare function createAuthorizationURL({
|
|
|
16
24
|
responseType,
|
|
17
25
|
display,
|
|
18
26
|
loginHint,
|
|
27
|
+
nonce,
|
|
19
28
|
hd,
|
|
20
29
|
responseMode,
|
|
21
30
|
additionalParams,
|
|
@@ -35,10 +44,11 @@ declare function createAuthorizationURL({
|
|
|
35
44
|
responseType?: string | undefined;
|
|
36
45
|
display?: string | undefined;
|
|
37
46
|
loginHint?: string | undefined;
|
|
47
|
+
nonce?: string | undefined;
|
|
38
48
|
hd?: string | undefined;
|
|
39
49
|
responseMode?: string | undefined;
|
|
40
50
|
additionalParams?: Record<string, string> | undefined;
|
|
41
51
|
scopeJoiner?: string | undefined;
|
|
42
52
|
}): Promise<URL>;
|
|
43
53
|
//#endregion
|
|
44
|
-
export { createAuthorizationURL };
|
|
54
|
+
export { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, createAuthorizationURL };
|
|
@@ -1,17 +1,36 @@
|
|
|
1
|
-
import { generateCodeChallenge } from "./utils.mjs";
|
|
1
|
+
import { generateCodeChallenge, getPrimaryClientId } from "./utils.mjs";
|
|
2
2
|
//#region src/oauth2/create-authorization-url.ts
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Query-parameter names that are populated by the framework as part of the
|
|
5
|
+
* authorization request and must not be overridden by caller-supplied
|
|
6
|
+
* `additionalParams`. Overriding `state`, PKCE, or `redirect_uri` would
|
|
7
|
+
* break the callback correlation and session pinning guarantees.
|
|
8
|
+
*/
|
|
9
|
+
const RESERVED_AUTHORIZATION_PARAMS = [
|
|
10
|
+
"state",
|
|
11
|
+
"client_id",
|
|
12
|
+
"redirect_uri",
|
|
13
|
+
"response_type",
|
|
14
|
+
"code_challenge",
|
|
15
|
+
"code_challenge_method",
|
|
16
|
+
"nonce",
|
|
17
|
+
"scope"
|
|
18
|
+
];
|
|
19
|
+
const RESERVED_AUTHORIZATION_PARAMS_SET = new Set(RESERVED_AUTHORIZATION_PARAMS);
|
|
20
|
+
async function createAuthorizationURL({ id, options, authorizationEndpoint, state, codeVerifier, scopes, claims, redirectURI, duration, prompt, accessType, responseType, display, loginHint, nonce, hd, responseMode, additionalParams, scopeJoiner }) {
|
|
4
21
|
options = typeof options === "function" ? await options() : options;
|
|
5
22
|
const url = new URL(options.authorizationEndpoint || authorizationEndpoint);
|
|
6
23
|
url.searchParams.set("response_type", responseType || "code");
|
|
7
|
-
const primaryClientId =
|
|
24
|
+
const primaryClientId = getPrimaryClientId(options.clientId);
|
|
25
|
+
if (!primaryClientId) throw new Error("OAuth provider requires clientId");
|
|
8
26
|
url.searchParams.set("client_id", primaryClientId);
|
|
9
27
|
url.searchParams.set("state", state);
|
|
10
|
-
if (scopes) url.searchParams.set("scope", scopes.join(scopeJoiner || " "));
|
|
28
|
+
if (scopes?.length) url.searchParams.set("scope", scopes.join(scopeJoiner || " "));
|
|
11
29
|
url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
|
|
12
30
|
duration && url.searchParams.set("duration", duration);
|
|
13
31
|
display && url.searchParams.set("display", display);
|
|
14
32
|
loginHint && url.searchParams.set("login_hint", loginHint);
|
|
33
|
+
nonce && url.searchParams.set("nonce", nonce);
|
|
15
34
|
prompt && url.searchParams.set("prompt", prompt);
|
|
16
35
|
hd && url.searchParams.set("hd", hd);
|
|
17
36
|
accessType && url.searchParams.set("access_type", accessType);
|
|
@@ -32,10 +51,11 @@ async function createAuthorizationURL({ id, options, authorizationEndpoint, stat
|
|
|
32
51
|
...claimsObj
|
|
33
52
|
} }));
|
|
34
53
|
}
|
|
35
|
-
if (additionalParams)
|
|
54
|
+
if (additionalParams) for (const [key, value] of Object.entries(additionalParams)) {
|
|
55
|
+
if (RESERVED_AUTHORIZATION_PARAMS_SET.has(key)) continue;
|
|
36
56
|
url.searchParams.set(key, value);
|
|
37
|
-
}
|
|
57
|
+
}
|
|
38
58
|
return url;
|
|
39
59
|
}
|
|
40
60
|
//#endregion
|
|
41
|
-
export { createAuthorizationURL };
|
|
61
|
+
export { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, createAuthorizationURL };
|