@vercel/connect 0.0.4-alpha.2 → 0.0.4-alpha.6
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 +23 -2
- package/dist/ash/connect-oauth.d.ts +67 -0
- package/dist/ash/connect-oauth.js +165 -0
- package/dist/ash/index.d.ts +1 -0
- package/dist/ash/index.js +1 -0
- package/dist/authjs/connect-provider.js +7 -18
- package/dist/authorization.js +1 -1
- package/dist/betterauth/connect-provider.js +5 -11
- package/dist/internal/team-id.js +1 -1
- package/dist/token.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -253,6 +253,27 @@ export default slackRoute({
|
|
|
253
253
|
|
|
254
254
|
The `webhookVerifier` is set to `vercelOidc()`, which authenticates inbound webhooks using the Vercel OIDC token issued for the calling project.
|
|
255
255
|
|
|
256
|
+
### `connectOAuth(options?)`
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
function connectOAuth(options?: ConnectOAuthOptions): AuthFn<Request>;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Returns an Ash route auth callback for Vercel Connect OAuth gateway access tokens. It verifies bearer JWTs issued by `https://connect.vercel.com`, requires the gateway access-token claim `typ: "at"`, and by default accepts Connect OAuth audiences shaped `teamId:VERCEL_PROJECT_ID:VERCEL_ENV`; the team id segment is ignored because project ids are globally unique.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { httpRoute } from "experimental-ash/channels/http";
|
|
266
|
+
import { connectOAuth } from "@vercel/connect/ash";
|
|
267
|
+
|
|
268
|
+
export default httpRoute({
|
|
269
|
+
auth: connectOAuth({
|
|
270
|
+
connectors: ["slack/mybot"],
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Pass `audiences` to accept explicit `aud` values, or pass `projectId` and `environment` to build the Connect OAuth audience manually. `connectors` accepts both `scl_...` connector ids and connector UIDs by checking each value against the `clientId` and `clientUid` claims; `tenantIds`, `installationIds`, `subjects`, and `claims` can further narrow accepted gateway tokens.
|
|
276
|
+
|
|
256
277
|
---
|
|
257
278
|
|
|
258
279
|
## `@vercel/connect/betterauth`
|
|
@@ -265,7 +286,7 @@ Better Auth provider for Vercel Connect's OAuth2 endpoints. Importing this subpa
|
|
|
265
286
|
function connect(options: BetterAuthConnectOptions): GenericOAuthConfig;
|
|
266
287
|
```
|
|
267
288
|
|
|
268
|
-
Returns a `GenericOAuthConfig` entry that drops into `genericOAuth({ config: [...] })` from `better-auth/plugins/generic-oauth`. Vercel Connect's OAuth client authentication is non-standard — the "client secret" is a Vercel OIDC token that has to be fetched per-request — so the helper overrides `getToken`
|
|
289
|
+
Returns a `GenericOAuthConfig` entry that drops into `genericOAuth({ config: [...] })` from `better-auth/plugins/generic-oauth`. Vercel Connect's OAuth client authentication is non-standard — the "client secret" is a Vercel OIDC token that has to be fetched per-request — so the helper overrides `getToken` to inject the token endpoint `Authorization` header.
|
|
269
290
|
|
|
270
291
|
```ts
|
|
271
292
|
import { betterAuth } from "better-auth";
|
|
@@ -309,7 +330,7 @@ Auth.js (NextAuth core) provider for Vercel Connect's OAuth2 endpoints. Importin
|
|
|
309
330
|
function connect(options: AuthJsConnectOptions): OAuth2Config<ConnectProfile>;
|
|
310
331
|
```
|
|
311
332
|
|
|
312
|
-
Returns an `OAuth2Config` provider entry for `AuthConfig.providers`. Vercel Connect's token endpoint expects the Vercel OIDC token in place of a static client secret, so the helper declares `token_endpoint_auth_method: "none"` and injects the
|
|
333
|
+
Returns an `OAuth2Config` provider entry for `AuthConfig.providers`. Vercel Connect's token endpoint expects the Vercel OIDC token in place of a static client secret, so the helper declares `token_endpoint_auth_method: "none"` and injects the token endpoint `Authorization` header via Auth.js's `customFetch` symbol.
|
|
313
334
|
|
|
314
335
|
```ts
|
|
315
336
|
import type { AuthConfig } from "@auth/core";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type AuthFn, type VerifyOidcConfig } from "experimental-ash/channels/auth";
|
|
2
|
+
export declare const CONNECT_OAUTH_ISSUER = "https://connect.vercel.com";
|
|
3
|
+
export type ConnectOAuthEnvironment = "production" | "preview" | "development";
|
|
4
|
+
export type ConnectOAuthAudienceEnvironment = ConnectOAuthEnvironment | "*";
|
|
5
|
+
export interface ConnectOAuthOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Exact `aud` claim values to accept. When omitted, the helper
|
|
8
|
+
* accepts Connect OAuth audiences shaped
|
|
9
|
+
* `teamId:projectId:environment` whose project id and environment
|
|
10
|
+
* match the current Vercel deployment.
|
|
11
|
+
*/
|
|
12
|
+
readonly audiences?: readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* Vercel project id used when `audiences` is omitted. Defaults to
|
|
15
|
+
* `VERCEL_PROJECT_ID`.
|
|
16
|
+
*/
|
|
17
|
+
readonly projectId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Vercel deployment environment used when `audiences` is omitted.
|
|
20
|
+
* Defaults to `VERCEL_TARGET_ENV`, then `VERCEL_ENV`. Use `"*"` to
|
|
21
|
+
* accept production, preview, and development audiences for the
|
|
22
|
+
* selected project.
|
|
23
|
+
*/
|
|
24
|
+
readonly environment?: ConnectOAuthAudienceEnvironment;
|
|
25
|
+
/**
|
|
26
|
+
* Optional gateway session id (`sub`) matchers. Patterns use Ash's
|
|
27
|
+
* IAM-style `*` wildcard matching.
|
|
28
|
+
*/
|
|
29
|
+
readonly subjects?: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* Optional Connect connector ids / UIDs to accept. Each value is
|
|
32
|
+
* matched against both the `clientId` and `clientUid` claims.
|
|
33
|
+
*/
|
|
34
|
+
readonly connectors?: readonly string[];
|
|
35
|
+
/** Optional upstream tenant ids (`tenantId` claim) to accept. */
|
|
36
|
+
readonly tenantIds?: readonly string[];
|
|
37
|
+
/**
|
|
38
|
+
* Optional connector installation ids (`installationId` claim) to
|
|
39
|
+
* accept.
|
|
40
|
+
*/
|
|
41
|
+
readonly installationIds?: readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Additional exact-match string claims to require. `typ` is always
|
|
44
|
+
* constrained to `"at"` for Connect OAuth gateway access tokens.
|
|
45
|
+
*/
|
|
46
|
+
readonly claims?: VerifyOidcConfig["claims"];
|
|
47
|
+
/**
|
|
48
|
+
* Override the OIDC discovery URL. Defaults to
|
|
49
|
+
* `https://connect.vercel.com/.well-known/openid-configuration`.
|
|
50
|
+
*/
|
|
51
|
+
readonly discoveryUrl?: string;
|
|
52
|
+
/** Clock skew in seconds. Defaults to Ash's OIDC verifier default. */
|
|
53
|
+
readonly clockSkewSeconds?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Returns an Ash route auth callback for Vercel Connect OAuth gateway
|
|
57
|
+
* access tokens.
|
|
58
|
+
*
|
|
59
|
+
* The accepted token must be a bearer JWT issued by
|
|
60
|
+
* `https://connect.vercel.com`, have the gateway access-token marker
|
|
61
|
+
* `typ: "at"`, and target one of the configured audiences. By default
|
|
62
|
+
* the audience is a Connect OAuth audience for the current Vercel
|
|
63
|
+
* project:
|
|
64
|
+
*
|
|
65
|
+
* `teamId:VERCEL_PROJECT_ID:VERCEL_ENV`
|
|
66
|
+
*/
|
|
67
|
+
export declare function connectOAuth(opts?: ConnectOAuthOptions): AuthFn<Request>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { extractBearerToken, verifyOidc, } from "experimental-ash/channels/auth";
|
|
2
|
+
export const CONNECT_OAUTH_ISSUER = "https://connect.vercel.com";
|
|
3
|
+
/**
|
|
4
|
+
* Returns an Ash route auth callback for Vercel Connect OAuth gateway
|
|
5
|
+
* access tokens.
|
|
6
|
+
*
|
|
7
|
+
* The accepted token must be a bearer JWT issued by
|
|
8
|
+
* `https://connect.vercel.com`, have the gateway access-token marker
|
|
9
|
+
* `typ: "at"`, and target one of the configured audiences. By default
|
|
10
|
+
* the audience is a Connect OAuth audience for the current Vercel
|
|
11
|
+
* project:
|
|
12
|
+
*
|
|
13
|
+
* `teamId:VERCEL_PROJECT_ID:VERCEL_ENV`
|
|
14
|
+
*/
|
|
15
|
+
export function connectOAuth(opts = {}) {
|
|
16
|
+
const audiencePolicy = resolveAudiencePolicy(opts);
|
|
17
|
+
const connectorPolicy = resolveConnectorPolicy(opts);
|
|
18
|
+
const claims = buildClaimMatchers(opts);
|
|
19
|
+
return async (request) => {
|
|
20
|
+
const token = extractBearerToken(request.headers.get("authorization"));
|
|
21
|
+
if (token === null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const payload = decodeJwtPayload(token);
|
|
25
|
+
if (payload === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const audiences = resolveRequestAudiences(payload, audiencePolicy);
|
|
29
|
+
if (audiences === null ||
|
|
30
|
+
!isConnectorAccepted(payload, connectorPolicy)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const result = await verifyOidc(token, {
|
|
34
|
+
audiences,
|
|
35
|
+
claims,
|
|
36
|
+
clockSkewSeconds: opts.clockSkewSeconds,
|
|
37
|
+
discoveryUrl: opts.discoveryUrl,
|
|
38
|
+
issuer: CONNECT_OAUTH_ISSUER,
|
|
39
|
+
subjects: opts.subjects,
|
|
40
|
+
});
|
|
41
|
+
return result.ok ? result.sessionAuth : null;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function resolveAudiencePolicy(opts) {
|
|
45
|
+
if (opts.audiences !== undefined) {
|
|
46
|
+
if (opts.audiences.length === 0) {
|
|
47
|
+
throw new Error("connectOAuth: audiences must not be empty.");
|
|
48
|
+
}
|
|
49
|
+
return { kind: "exact", audiences: opts.audiences };
|
|
50
|
+
}
|
|
51
|
+
const projectId = opts.projectId ?? readNonEmptyEnv("VERCEL_PROJECT_ID");
|
|
52
|
+
const environment = opts.environment ?? inferVercelEnvironment();
|
|
53
|
+
if (projectId === undefined) {
|
|
54
|
+
throw new Error("connectOAuth: could not infer projectId from VERCEL_PROJECT_ID; pass audiences or projectId explicitly.");
|
|
55
|
+
}
|
|
56
|
+
if (environment === undefined) {
|
|
57
|
+
throw new Error("connectOAuth: could not infer environment from VERCEL_TARGET_ENV or VERCEL_ENV; pass audiences or environment explicitly.");
|
|
58
|
+
}
|
|
59
|
+
const environments = environment === "*"
|
|
60
|
+
? ["production", "preview", "development"]
|
|
61
|
+
: [environment];
|
|
62
|
+
return { kind: "project", environments, projectId };
|
|
63
|
+
}
|
|
64
|
+
function resolveRequestAudiences(payload, policy) {
|
|
65
|
+
if (policy.kind === "exact") {
|
|
66
|
+
return policy.audiences;
|
|
67
|
+
}
|
|
68
|
+
const audiences = extractAudiences(payload);
|
|
69
|
+
const accepted = audiences.filter((audience) => {
|
|
70
|
+
const parsed = parseConnectOAuthAudience(audience);
|
|
71
|
+
return (parsed !== null &&
|
|
72
|
+
parsed.projectId === policy.projectId &&
|
|
73
|
+
policy.environments.includes(parsed.environment));
|
|
74
|
+
});
|
|
75
|
+
return accepted.length === 0 ? null : accepted;
|
|
76
|
+
}
|
|
77
|
+
function resolveConnectorPolicy(opts) {
|
|
78
|
+
if (opts.connectors === undefined) {
|
|
79
|
+
return { kind: "any" };
|
|
80
|
+
}
|
|
81
|
+
if (opts.connectors.length === 0) {
|
|
82
|
+
throw new Error("connectOAuth: connectors must not be empty.");
|
|
83
|
+
}
|
|
84
|
+
return { kind: "connectors", connectors: opts.connectors };
|
|
85
|
+
}
|
|
86
|
+
function isConnectorAccepted(payload, policy) {
|
|
87
|
+
if (policy.kind === "any") {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const clientId = stringClaim(payload, "clientId");
|
|
91
|
+
if (clientId !== undefined && policy.connectors.includes(clientId)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
const clientUid = stringClaim(payload, "clientUid");
|
|
95
|
+
return clientUid !== undefined && policy.connectors.includes(clientUid);
|
|
96
|
+
}
|
|
97
|
+
function inferVercelEnvironment() {
|
|
98
|
+
const value = readNonEmptyEnv("VERCEL_TARGET_ENV") ?? readNonEmptyEnv("VERCEL_ENV");
|
|
99
|
+
if (value === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
assertEnvironment(value);
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
function buildClaimMatchers(opts) {
|
|
105
|
+
return {
|
|
106
|
+
...opts.claims,
|
|
107
|
+
...(opts.tenantIds === undefined ? {} : { tenantId: opts.tenantIds }),
|
|
108
|
+
...(opts.installationIds === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { installationId: opts.installationIds }),
|
|
111
|
+
typ: ["at"],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function readNonEmptyEnv(name) {
|
|
115
|
+
const value = typeof process === "undefined" ? undefined : process.env?.[name]?.trim();
|
|
116
|
+
return value === undefined || value.length === 0 ? undefined : value;
|
|
117
|
+
}
|
|
118
|
+
function extractAudiences(payload) {
|
|
119
|
+
const aud = payload.aud;
|
|
120
|
+
if (typeof aud === "string") {
|
|
121
|
+
return [aud];
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(aud)) {
|
|
124
|
+
return aud.filter((value) => typeof value === "string");
|
|
125
|
+
}
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
function stringClaim(payload, claim) {
|
|
129
|
+
const value = payload[claim];
|
|
130
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
131
|
+
}
|
|
132
|
+
function decodeJwtPayload(token) {
|
|
133
|
+
try {
|
|
134
|
+
const payload = token.split(".")[1];
|
|
135
|
+
if (payload === undefined)
|
|
136
|
+
return null;
|
|
137
|
+
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
138
|
+
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
139
|
+
const decoded = JSON.parse(atob(padded));
|
|
140
|
+
return typeof decoded === "object" && decoded !== null ? decoded : null;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function parseConnectOAuthAudience(audience) {
|
|
147
|
+
const [teamId, projectId, environment, extra] = audience.split(":");
|
|
148
|
+
if (!teamId || !projectId || !environment || extra !== undefined) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
if (environment !== "production" &&
|
|
152
|
+
environment !== "preview" &&
|
|
153
|
+
environment !== "development") {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return { projectId, environment };
|
|
157
|
+
}
|
|
158
|
+
function assertEnvironment(value) {
|
|
159
|
+
if (value !== "production" &&
|
|
160
|
+
value !== "preview" &&
|
|
161
|
+
value !== "development" &&
|
|
162
|
+
value !== "*") {
|
|
163
|
+
throw new Error(`connectOAuth: invalid environment ${JSON.stringify(value)}; expected "production", "preview", "development", or "*".`);
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/ash/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
* everything from `@vercel/connect/ash`.
|
|
8
8
|
*/
|
|
9
9
|
export { connect, type AshAuthorizationInput, type AshAuthorizationOptions, type ConnectAuthorizationPhase, type ConnectAuthorizationState, } from "./connection-authorization.js";
|
|
10
|
+
export { CONNECT_OAUTH_ISSUER, connectOAuth, type ConnectOAuthAudienceEnvironment, type ConnectOAuthEnvironment, type ConnectOAuthOptions, } from "./connect-oauth.js";
|
|
10
11
|
export { connectSlackCredentials } from "./slack-credentials.js";
|
package/dist/ash/index.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* in `AuthConfig.providers`. Vercel Connect's OAuth client
|
|
6
6
|
* authentication is non-standard — the "client secret" is a
|
|
7
7
|
* per-request Vercel OIDC token — so we declare
|
|
8
|
-
* `token_endpoint_auth_method: "none"` and inject the
|
|
9
|
-
* Authorization
|
|
8
|
+
* `token_endpoint_auth_method: "none"` and inject the token endpoint
|
|
9
|
+
* Authorization header via the `customFetch` symbol.
|
|
10
10
|
*
|
|
11
11
|
* ```ts
|
|
12
12
|
* import type { AuthConfig } from "@auth/core";
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
import { customFetch } from "@auth/core";
|
|
27
27
|
import { getVercelOidcToken } from "@vercel/oidc";
|
|
28
28
|
import { tryGetVercelTeamId, withTeamId } from "../internal/team-id.js";
|
|
29
|
-
const CONNECT_AUTHORIZATION_URL = "https://vercel.com/
|
|
30
|
-
const CONNECT_TOKEN_URL = "https://vercel.com/
|
|
31
|
-
const CONNECT_USERINFO_URL = "https://vercel.com/
|
|
29
|
+
const CONNECT_AUTHORIZATION_URL = "https://connect.vercel.com/oauth/authorize";
|
|
30
|
+
const CONNECT_TOKEN_URL = "https://connect.vercel.com/oauth/token";
|
|
31
|
+
const CONNECT_USERINFO_URL = "https://connect.vercel.com/oauth/userinfo";
|
|
32
32
|
/**
|
|
33
33
|
* Builds an Auth.js `OAuth2Config` for Vercel Connect. Push the
|
|
34
34
|
* result into `AuthConfig.providers`.
|
|
@@ -43,9 +43,8 @@ export function connect(options) {
|
|
|
43
43
|
clientId: options.connector,
|
|
44
44
|
// The token endpoint expects client_secret_basic, but the secret
|
|
45
45
|
// is a Vercel OIDC token that has to be fetched per-request. We
|
|
46
|
-
// rely on the customFetch hook below to inject the
|
|
47
|
-
// Authorization header
|
|
48
|
-
// declared "none" here.
|
|
46
|
+
// rely on the customFetch hook below to inject the token endpoint
|
|
47
|
+
// Authorization header.
|
|
49
48
|
client: { token_endpoint_auth_method: "none" },
|
|
50
49
|
authorization: {
|
|
51
50
|
// Best-effort: scope the Vercel Connect consent UI to the
|
|
@@ -79,16 +78,6 @@ export function connect(options) {
|
|
|
79
78
|
headers.set("authorization", `Basic ${btoa(credentials)}`);
|
|
80
79
|
return fetch(input, { ...init, headers });
|
|
81
80
|
}
|
|
82
|
-
if (url.startsWith(CONNECT_USERINFO_URL)) {
|
|
83
|
-
const headers = new Headers(init?.headers);
|
|
84
|
-
const accessToken = (headers.get("authorization") ?? "").replace(/^Bearer\s+/i, "");
|
|
85
|
-
const vercelToken = await fetchVercelToken();
|
|
86
|
-
const triple = [options.connector, vercelToken, accessToken]
|
|
87
|
-
.map(encodeURIComponent)
|
|
88
|
-
.join(":");
|
|
89
|
-
headers.set("authorization", `Bearer ${btoa(triple)}`);
|
|
90
|
-
return fetch(input, { ...init, headers });
|
|
91
|
-
}
|
|
92
81
|
return fetch(input, init);
|
|
93
82
|
},
|
|
94
83
|
};
|
package/dist/authorization.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function startAuthorization(connector, params, options) {
|
|
|
10
10
|
validateWebhookUrl(options.webhook);
|
|
11
11
|
}
|
|
12
12
|
const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
|
|
13
|
-
const endpoint = `https://api.vercel.com/v1/
|
|
13
|
+
const endpoint = `https://api.vercel.com/v1/connect/authorize/${encodeURIComponent(connector)}`;
|
|
14
14
|
const body = {
|
|
15
15
|
...params,
|
|
16
16
|
...(options?.callbackUrl !== undefined && {
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* `better-auth/plugins/generic-oauth`. Vercel Connect's OAuth client
|
|
7
7
|
* authentication is non-standard — the "client secret" is a Vercel
|
|
8
8
|
* OIDC token that has to be fetched per-request — so we override
|
|
9
|
-
* `getToken`
|
|
10
|
-
* headers instead of relying on Better Auth's built-in flow.
|
|
9
|
+
* `getToken` to inject the token endpoint Authorization header.
|
|
11
10
|
*
|
|
12
11
|
* ```ts
|
|
13
12
|
* import { betterAuth } from "better-auth";
|
|
@@ -31,9 +30,9 @@
|
|
|
31
30
|
import { getVercelOidcToken } from "@vercel/oidc";
|
|
32
31
|
import { getOAuth2Tokens } from "better-auth/oauth2";
|
|
33
32
|
import { tryGetVercelTeamId, withTeamId } from "../internal/team-id.js";
|
|
34
|
-
const CONNECT_AUTHORIZATION_URL = "https://vercel.com/
|
|
35
|
-
const CONNECT_TOKEN_URL = "https://vercel.com/
|
|
36
|
-
const CONNECT_USERINFO_URL = "https://vercel.com/
|
|
33
|
+
const CONNECT_AUTHORIZATION_URL = "https://connect.vercel.com/oauth/authorize";
|
|
34
|
+
const CONNECT_TOKEN_URL = "https://connect.vercel.com/oauth/token";
|
|
35
|
+
const CONNECT_USERINFO_URL = "https://connect.vercel.com/oauth/userinfo";
|
|
37
36
|
/**
|
|
38
37
|
* Builds a `GenericOAuthConfig` entry for Vercel Connect. Pass the
|
|
39
38
|
* result into `genericOAuth({ config: [...] })`.
|
|
@@ -83,13 +82,8 @@ export function connect(options) {
|
|
|
83
82
|
if (!tokens.accessToken) {
|
|
84
83
|
throw new Error("Missing access token for Vercel Connect userinfo request");
|
|
85
84
|
}
|
|
86
|
-
const vercelToken = await fetchVercelToken();
|
|
87
|
-
const triple = [options.connector, vercelToken, tokens.accessToken]
|
|
88
|
-
.map(encodeURIComponent)
|
|
89
|
-
.join(":");
|
|
90
|
-
const bearerToken = btoa(triple);
|
|
91
85
|
const response = await fetch(CONNECT_USERINFO_URL, {
|
|
92
|
-
headers: { Authorization: `Bearer ${
|
|
86
|
+
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
|
93
87
|
});
|
|
94
88
|
if (!response.ok) {
|
|
95
89
|
throw new Error(`Failed to fetch Vercel Connect user info: ${response.status} ${response.statusText}: ${await response.text()}`);
|
package/dist/internal/team-id.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Best-effort Vercel team-id extraction for the OAuth adapters.
|
|
3
3
|
*
|
|
4
|
-
* Vercel Connect's `/
|
|
4
|
+
* Vercel Connect's `/oauth/authorize` endpoint accepts an
|
|
5
5
|
* optional `?teamId=...` query parameter that scopes the consent UI
|
|
6
6
|
* to a specific team. The team id lives inside the Vercel OIDC
|
|
7
7
|
* token's `owner_id` claim, so we decode the JWT payload (no
|
package/dist/token.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function getTokenResponse(connector, params, options) {
|
|
|
34
34
|
cache.delete(cacheKey);
|
|
35
35
|
}
|
|
36
36
|
const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
|
|
37
|
-
const endpoint = `https://api.vercel.com/v1/
|
|
37
|
+
const endpoint = `https://api.vercel.com/v1/connect/token/${encodeURIComponent(connector)}`;
|
|
38
38
|
const response = await fetch(endpoint, {
|
|
39
39
|
method: "POST",
|
|
40
40
|
headers: {
|