@vercel/connect 2.3.3 → 2.3.5

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.
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { customFetch } from '@auth/core';
27
27
  import { getVercelOidcToken } from '@vercel/oidc';
28
- import { tryGetVercelTeamId, withTeamId } from '../internal/team-id.js';
28
+ import { tryGetVercelAuthorizationContext, withAuthorizationContext, } from '../internal/authorization-context.js';
29
29
  const CONNECT_AUTHORIZATION_URL = 'https://connect.vercel.com/oauth/authorize';
30
30
  const CONNECT_TOKEN_URL = 'https://connect.vercel.com/oauth/token';
31
31
  const CONNECT_USERINFO_URL = 'https://connect.vercel.com/oauth/userinfo';
@@ -49,10 +49,10 @@ export function connect(options) {
49
49
  client: { token_endpoint_auth_method: 'none' },
50
50
  authorization: {
51
51
  // Best-effort: scope the Vercel Connect consent UI to the
52
- // deployment's Vercel team when we can read it from the OIDC
53
- // token. Falls back to the unqualified URL when the token isn't
54
- // present (e.g. running outside Vercel).
55
- url: withTeamId(CONNECT_AUTHORIZATION_URL, tryGetVercelTeamId()),
52
+ // deployment's team, project, and environment from the OIDC token.
53
+ // Falls back to the unqualified URL when the token isn't present
54
+ // (e.g. running outside Vercel).
55
+ url: withAuthorizationContext(CONNECT_AUTHORIZATION_URL, tryGetVercelAuthorizationContext()),
56
56
  params: { scope },
57
57
  },
58
58
  token: CONNECT_TOKEN_URL,
@@ -29,7 +29,7 @@
29
29
  */
30
30
  import { getVercelOidcToken } from '@vercel/oidc';
31
31
  import { getOAuth2Tokens } from 'better-auth/oauth2';
32
- import { tryGetVercelTeamId, withTeamId } from '../internal/team-id.js';
32
+ import { tryGetVercelAuthorizationContext, withAuthorizationContext, } from '../internal/authorization-context.js';
33
33
  import { ConnectError, createConnectErrorFromResponse } from '../token.js';
34
34
  const CONNECT_AUTHORIZATION_URL = 'https://connect.vercel.com/oauth/authorize';
35
35
  const CONNECT_TOKEN_URL = 'https://connect.vercel.com/oauth/token';
@@ -46,10 +46,10 @@ export function connect(options) {
46
46
  providerId: options.providerId,
47
47
  clientId: options.connector,
48
48
  // Best-effort: scope the Vercel Connect consent UI to the
49
- // deployment's Vercel team when we can read it from the OIDC
50
- // token. Falls back to the unqualified URL when the token isn't
51
- // present (e.g. running outside Vercel).
52
- authorizationUrl: withTeamId(CONNECT_AUTHORIZATION_URL, tryGetVercelTeamId()),
49
+ // deployment's team, project, and environment from the OIDC token.
50
+ // Falls back to the unqualified URL when the token isn't present
51
+ // (e.g. running outside Vercel).
52
+ authorizationUrl: withAuthorizationContext(CONNECT_AUTHORIZATION_URL, tryGetVercelAuthorizationContext()),
53
53
  scopes,
54
54
  pkce: true,
55
55
  tokenUrl: CONNECT_TOKEN_URL,
@@ -0,0 +1,9 @@
1
+ interface AuthorizationContext {
2
+ teamId?: string;
3
+ projectId?: string;
4
+ environment?: string;
5
+ }
6
+ export declare function tryGetVercelAuthorizationContext(): AuthorizationContext | undefined;
7
+ export declare function extractAuthorizationContextFromOidcToken(token: string): AuthorizationContext | undefined;
8
+ export declare function withAuthorizationContext(baseUrl: string, context: AuthorizationContext | undefined): string;
9
+ export {};
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Best-effort Vercel authorization context extraction for the OAuth adapters.
3
+ *
4
+ * Vercel Connect's `/oauth/authorize` endpoint accepts an
5
+ * optional team id, project id, and environment to scope the consent UI.
6
+ * These values come from the Vercel OIDC token's `owner_id`, `project_id`,
7
+ * and `custom_environment_id` or `environment` claims. We decode the JWT
8
+ * payload without signature verification — we're reading our own token.
9
+ *
10
+ * Failure is silent: the helper returns `undefined` if the token
11
+ * isn't available (e.g. running outside Vercel) or the payload can't
12
+ * be decoded. Adapters then fall back to an unqualified
13
+ * authorization URL.
14
+ */
15
+ import { getVercelOidcTokenSync } from '@vercel/oidc';
16
+ export function tryGetVercelAuthorizationContext() {
17
+ let token;
18
+ try {
19
+ token = getVercelOidcTokenSync();
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ return extractAuthorizationContextFromOidcToken(token);
25
+ }
26
+ export function extractAuthorizationContextFromOidcToken(token) {
27
+ try {
28
+ const parts = token.split('.');
29
+ if (parts.length < 2)
30
+ return undefined;
31
+ const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');
32
+ const padLen = (4 - (padded.length % 4)) % 4;
33
+ const claims = JSON.parse(atob(padded + '='.repeat(padLen)));
34
+ const environment = claims.custom_environment_id || claims.environment;
35
+ return {
36
+ teamId: typeof claims.owner_id === 'string' ? claims.owner_id : undefined,
37
+ projectId: typeof claims.project_id === 'string' ? claims.project_id : undefined,
38
+ environment: typeof environment === 'string' ? environment : undefined,
39
+ };
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
45
+ export function withAuthorizationContext(baseUrl, context) {
46
+ if (!context)
47
+ return baseUrl;
48
+ const url = new URL(baseUrl);
49
+ if (context.teamId)
50
+ url.searchParams.set('teamId', context.teamId);
51
+ if (context.projectId)
52
+ url.searchParams.set('projectId', context.projectId);
53
+ if (context.environment) {
54
+ url.searchParams.set('environment', context.environment);
55
+ }
56
+ return url.toString();
57
+ }
package/dist/token.d.ts CHANGED
@@ -8,6 +8,15 @@ export interface ConnectUserTokenSubject {
8
8
  type: 'user';
9
9
  id: string;
10
10
  issuer?: string;
11
+ /**
12
+ * When specified, `id` and `issuer` identify the user in the namespace of
13
+ * this IDP connector. The connector must be configured as an IDP.
14
+ * The XAA/ID-JAG protocol is used to resolve the target access token when
15
+ * configured by the target connector.
16
+ *
17
+ * @experimental Subject to change.
18
+ */
19
+ experimental_idp?: string;
11
20
  }
12
21
  export interface ConnectJwtBearerTokenSubject {
13
22
  type: 'jwt-bearer';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/connect",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,3 +0,0 @@
1
- export declare function tryGetVercelTeamId(): string | undefined;
2
- export declare function extractTeamIdFromOidcToken(token: string): string | undefined;
3
- export declare function withTeamId(baseUrl: string, teamId: string | undefined): string;
@@ -1,47 +0,0 @@
1
- /**
2
- * Best-effort Vercel team-id extraction for the OAuth adapters.
3
- *
4
- * Vercel Connect's `/oauth/authorize` endpoint accepts an
5
- * optional `?teamId=...` query parameter that scopes the consent UI
6
- * to a specific team. The team id lives inside the Vercel OIDC
7
- * token's `owner_id` claim, so we decode the JWT payload (no
8
- * signature verification — we're reading our own token) and pull it
9
- * out.
10
- *
11
- * Failure is silent: the helper returns `undefined` if the token
12
- * isn't available (e.g. running outside Vercel) or the payload can't
13
- * be decoded. Adapters then fall back to an unqualified
14
- * authorization URL.
15
- */
16
- import { getVercelOidcTokenSync } from '@vercel/oidc';
17
- export function tryGetVercelTeamId() {
18
- let token;
19
- try {
20
- token = getVercelOidcTokenSync();
21
- }
22
- catch {
23
- return undefined;
24
- }
25
- return extractTeamIdFromOidcToken(token);
26
- }
27
- export function extractTeamIdFromOidcToken(token) {
28
- try {
29
- const parts = token.split('.');
30
- if (parts.length < 2)
31
- return undefined;
32
- const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');
33
- const padLen = (4 - (padded.length % 4)) % 4;
34
- const claims = JSON.parse(atob(padded + '='.repeat(padLen)));
35
- return typeof claims.owner_id === 'string' ? claims.owner_id : undefined;
36
- }
37
- catch {
38
- return undefined;
39
- }
40
- }
41
- export function withTeamId(baseUrl, teamId) {
42
- if (!teamId)
43
- return baseUrl;
44
- const url = new URL(baseUrl);
45
- url.searchParams.set('teamId', teamId);
46
- return url.toString();
47
- }