@vercel/oidc 3.6.0 → 3.6.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @vercel/oidc
2
2
 
3
+ ## 3.6.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3f21605]
8
+ - @vercel/cli-exec@1.0.0
9
+
10
+ ## 3.6.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 01cf6c2: Add `verifyVercelOidcToken` for verifying Vercel OIDC tokens against Vercel's remote JWKS.
15
+
3
16
  ## 3.6.0
4
17
 
5
18
  ### Minor Changes
@@ -1,4 +1,5 @@
1
1
  export { getContext } from './get-context';
2
+ export { verifyVercelOidcToken, type VercelOidcPayload, } from './verify-vercel-oidc-token';
2
3
  export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors';
3
4
  export declare function getVercelOidcToken(): Promise<string>;
4
5
  export declare function getVercelOidcTokenSync(): string;
@@ -23,10 +23,12 @@ __export(index_browser_exports, {
23
23
  getContext: () => import_get_context.getContext,
24
24
  getVercelOidcToken: () => getVercelOidcToken,
25
25
  getVercelOidcTokenSync: () => getVercelOidcTokenSync,
26
- getVercelToken: () => getVercelToken
26
+ getVercelToken: () => getVercelToken,
27
+ verifyVercelOidcToken: () => import_verify_vercel_oidc_token.verifyVercelOidcToken
27
28
  });
28
29
  module.exports = __toCommonJS(index_browser_exports);
29
30
  var import_get_context = require("./get-context");
31
+ var import_verify_vercel_oidc_token = require("./verify-vercel-oidc-token");
30
32
  var import_auth_errors = require("./auth-errors");
31
33
  async function getVercelOidcToken() {
32
34
  return "";
@@ -44,5 +46,6 @@ async function getVercelToken() {
44
46
  getContext,
45
47
  getVercelOidcToken,
46
48
  getVercelOidcTokenSync,
47
- getVercelToken
49
+ getVercelToken,
50
+ verifyVercelOidcToken
48
51
  });
@@ -1,4 +1,5 @@
1
1
  export { getContext } from './get-context';
2
+ export { verifyVercelOidcToken, type VercelOidcPayload, } from './verify-vercel-oidc-token';
2
3
  export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors';
3
4
  export { getVercelOidcTokenSync } from './get-vercel-oidc-token-sync';
4
5
  /**
@@ -23,11 +23,13 @@ __export(index_edge_light_exports, {
23
23
  getContext: () => import_get_context.getContext,
24
24
  getVercelOidcToken: () => getVercelOidcToken,
25
25
  getVercelOidcTokenSync: () => import_get_vercel_oidc_token_sync2.getVercelOidcTokenSync,
26
- getVercelToken: () => getVercelToken
26
+ getVercelToken: () => getVercelToken,
27
+ verifyVercelOidcToken: () => import_verify_vercel_oidc_token.verifyVercelOidcToken
27
28
  });
28
29
  module.exports = __toCommonJS(index_edge_light_exports);
29
30
  var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync");
30
31
  var import_get_context = require("./get-context");
32
+ var import_verify_vercel_oidc_token = require("./verify-vercel-oidc-token");
31
33
  var import_auth_errors = require("./auth-errors");
32
34
  var import_get_vercel_oidc_token_sync2 = require("./get-vercel-oidc-token-sync");
33
35
  async function getVercelOidcToken() {
@@ -43,5 +45,6 @@ async function getVercelToken() {
43
45
  getContext,
44
46
  getVercelOidcToken,
45
47
  getVercelOidcTokenSync,
46
- getVercelToken
48
+ getVercelToken,
49
+ verifyVercelOidcToken
47
50
  });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { getVercelOidcToken } from './get-vercel-oidc-token-with-refresh';
2
2
  export { getVercelOidcTokenSync } from './get-vercel-oidc-token-sync';
3
3
  export { getContext } from './get-context';
4
+ export { verifyVercelOidcToken, type VercelOidcPayload, } from './verify-vercel-oidc-token';
4
5
  export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors';
5
6
  export { getVercelToken } from './token-util';
package/dist/index.js CHANGED
@@ -23,12 +23,14 @@ __export(src_exports, {
23
23
  getContext: () => import_get_context.getContext,
24
24
  getVercelOidcToken: () => import_get_vercel_oidc_token_with_refresh.getVercelOidcToken,
25
25
  getVercelOidcTokenSync: () => import_get_vercel_oidc_token_sync.getVercelOidcTokenSync,
26
- getVercelToken: () => import_token_util.getVercelToken
26
+ getVercelToken: () => import_token_util.getVercelToken,
27
+ verifyVercelOidcToken: () => import_verify_vercel_oidc_token.verifyVercelOidcToken
27
28
  });
28
29
  module.exports = __toCommonJS(src_exports);
29
30
  var import_get_vercel_oidc_token_with_refresh = require("./get-vercel-oidc-token-with-refresh");
30
31
  var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync");
31
32
  var import_get_context = require("./get-context");
33
+ var import_verify_vercel_oidc_token = require("./verify-vercel-oidc-token");
32
34
  var import_auth_errors = require("./auth-errors");
33
35
  var import_token_util = require("./token-util");
34
36
  // Annotate the CommonJS export names for ESM import in node:
@@ -38,5 +40,6 @@ var import_token_util = require("./token-util");
38
40
  getContext,
39
41
  getVercelOidcToken,
40
42
  getVercelOidcTokenSync,
41
- getVercelToken
43
+ getVercelToken,
44
+ verifyVercelOidcToken
42
45
  });
@@ -0,0 +1,42 @@
1
+ import { type JWTVerifyOptions, type JWTVerifyResult, type JWTPayload } from 'jose';
2
+ export type VercelOidcPayload = JWTPayload & {
3
+ owner_id: string;
4
+ project_id: string;
5
+ environment: string;
6
+ external_sub?: string;
7
+ sub: string;
8
+ aud: string;
9
+ iss: string;
10
+ };
11
+ /**
12
+ * Verifies a Vercel OIDC token against Vercel's remote JWKS.
13
+ *
14
+ * The issuer must be `https://oidc.vercel.com` or start with
15
+ * `https://oidc.vercel.com/`. The JWKS is always
16
+ * `https://oidc.vercel.com/.well-known/jwks`.
17
+ *
18
+ * Options:
19
+ *
20
+ * - `issuer`: Expected `iss` claim verified by Jose. The verified issuer must
21
+ * still be `https://oidc.vercel.com` or start with
22
+ * `https://oidc.vercel.com/`.
23
+ * - `projectId`: Expected `project_id` claim or claims. Defaults to
24
+ * `process.env.VERCEL_PROJECT_ID`. Pass an array to allow any matching
25
+ * project ID. Pass `'*'` to allow any project ID. When `projectId` is `'*'`,
26
+ * either `ownerId` or `audience` is required.
27
+ * - `environment`: Expected `environment` claim or claims. Defaults to
28
+ * `process.env.VERCEL_TARGET_ENV || process.env.VERCEL_ENV`. Pass an array
29
+ * to allow any matching environment. Pass `'*'` to allow any environment.
30
+ * - `ownerId`: Expected `owner_id` claim. When omitted, the claim is not
31
+ * checked.
32
+ * - Any other Jose JWT verification option.
33
+ *
34
+ * @param token The Vercel OIDC token to verify.
35
+ * @param options Optional Jose JWT verification options.
36
+ * @returns Jose's verified JWT result.
37
+ */
38
+ export declare function verifyVercelOidcToken<PayloadType = VercelOidcPayload>(token: string, options?: {
39
+ projectId?: string | string[] | '*';
40
+ environment?: string | string[] | '*';
41
+ ownerId?: string;
42
+ } & JWTVerifyOptions): Promise<JWTVerifyResult<PayloadType>>;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var verify_vercel_oidc_token_exports = {};
20
+ __export(verify_vercel_oidc_token_exports, {
21
+ verifyVercelOidcToken: () => verifyVercelOidcToken
22
+ });
23
+ module.exports = __toCommonJS(verify_vercel_oidc_token_exports);
24
+ var import_jose = require("jose");
25
+ const VERCEL_OIDC_ISSUER = "https://oidc.vercel.com";
26
+ const VERCEL_OIDC_JWKS_URL = new URL(
27
+ "https://oidc.vercel.com/.well-known/jwks"
28
+ );
29
+ const DEFAULT_ALGORITHMS = ["RS256"];
30
+ const VERCEL_OIDC_JWKS = (0, import_jose.createRemoteJWKSet)(VERCEL_OIDC_JWKS_URL);
31
+ async function verifyVercelOidcToken(token, options) {
32
+ const {
33
+ algorithms,
34
+ projectId = process.env.VERCEL_PROJECT_ID,
35
+ environment = process.env.VERCEL_TARGET_ENV || process.env.VERCEL_ENV,
36
+ ownerId,
37
+ ...verifyOptions
38
+ } = options ?? {};
39
+ if (projectId === "*" && ownerId === void 0 && !hasAudienceVerification(verifyOptions.audience)) {
40
+ throw new TypeError(
41
+ "Expected ownerId or audience to be provided when projectId is '*'."
42
+ );
43
+ }
44
+ const result = await (0, import_jose.jwtVerify)(token, VERCEL_OIDC_JWKS, {
45
+ ...verifyOptions,
46
+ algorithms: algorithms ?? DEFAULT_ALGORITHMS
47
+ });
48
+ validateIssuer(result.payload.iss);
49
+ validateClaim({
50
+ actual: result.payload.project_id,
51
+ claim: "project_id",
52
+ env: "VERCEL_PROJECT_ID",
53
+ expected: projectId,
54
+ option: "projectId"
55
+ });
56
+ validateClaim({
57
+ actual: result.payload.environment,
58
+ claim: "environment",
59
+ env: "VERCEL_TARGET_ENV or VERCEL_ENV",
60
+ expected: environment,
61
+ option: "environment"
62
+ });
63
+ validateOptionalClaim({
64
+ actual: result.payload.owner_id,
65
+ claim: "owner_id",
66
+ expected: ownerId
67
+ });
68
+ return result;
69
+ }
70
+ function hasAudienceVerification(audience) {
71
+ return Array.isArray(audience) ? audience.length > 0 : audience !== void 0;
72
+ }
73
+ function validateIssuer(actual) {
74
+ if (actual !== VERCEL_OIDC_ISSUER && (typeof actual !== "string" || !actual.startsWith(`${VERCEL_OIDC_ISSUER}/`))) {
75
+ throw new TypeError(
76
+ `Expected Vercel OIDC token iss claim to be "${VERCEL_OIDC_ISSUER}" or to start with "${VERCEL_OIDC_ISSUER}/".`
77
+ );
78
+ }
79
+ }
80
+ function validateClaim({
81
+ actual,
82
+ claim,
83
+ env,
84
+ expected,
85
+ option
86
+ }) {
87
+ if (expected === "*") {
88
+ return;
89
+ }
90
+ if (expected === void 0 || expected.length === 0) {
91
+ throw new TypeError(
92
+ `Expected ${env} to be set or ${option} to be provided. Pass ${option}: '*' to allow any ${claim} claim.`
93
+ );
94
+ }
95
+ if (Array.isArray(expected) && typeof actual === "string" && expected.includes(actual)) {
96
+ return;
97
+ }
98
+ if (actual !== expected) {
99
+ throw new TypeError(
100
+ Array.isArray(expected) ? `Expected Vercel OIDC token ${claim} claim to be one of: ${expected.map((value) => `"${value}"`).join(", ")}.` : `Expected Vercel OIDC token ${claim} claim to be "${expected}".`
101
+ );
102
+ }
103
+ }
104
+ function validateOptionalClaim({
105
+ actual,
106
+ claim,
107
+ expected
108
+ }) {
109
+ if (expected === void 0) {
110
+ return;
111
+ }
112
+ if (actual !== expected) {
113
+ throw new TypeError(
114
+ `Expected Vercel OIDC token ${claim} claim to be "${expected}".`
115
+ );
116
+ }
117
+ }
118
+ // Annotate the CommonJS export names for ESM import in node:
119
+ 0 && (module.exports = {
120
+ verifyVercelOidcToken
121
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_jose = require("jose");
4
+ var import_verify_vercel_oidc_token = require("./verify-vercel-oidc-token");
5
+ const mocks = import_vitest.vi.hoisted(() => {
6
+ const remoteJwks = import_vitest.vi.fn();
7
+ return {
8
+ remoteJwks,
9
+ createRemoteJWKSet: import_vitest.vi.fn(() => remoteJwks),
10
+ jwtVerify: import_vitest.vi.fn()
11
+ };
12
+ });
13
+ import_vitest.vi.mock("jose", () => ({
14
+ createRemoteJWKSet: mocks.createRemoteJWKSet,
15
+ jwtVerify: mocks.jwtVerify
16
+ }));
17
+ (0, import_vitest.describe)("verifyVercelOidcToken", () => {
18
+ const verifyResult = {
19
+ payload: {
20
+ sub: "owner:team1:project:site:environment:production",
21
+ iss: "https://oidc.vercel.com/team1",
22
+ aud: "https://oidc.vercel.com/team1",
23
+ project_id: "prj_test",
24
+ environment: "production",
25
+ owner_id: "team_team1"
26
+ },
27
+ protectedHeader: {
28
+ alg: "RS256"
29
+ }
30
+ };
31
+ (0, import_vitest.beforeEach)(() => {
32
+ process.env.VERCEL_PROJECT_ID = "prj_test";
33
+ process.env.VERCEL_TARGET_ENV = "production";
34
+ delete process.env.VERCEL_ENV;
35
+ import_vitest.vi.mocked(import_jose.jwtVerify).mockClear();
36
+ mockVerifiedPayload();
37
+ });
38
+ (0, import_vitest.afterEach)(() => {
39
+ delete process.env.VERCEL_PROJECT_ID;
40
+ delete process.env.VERCEL_TARGET_ENV;
41
+ delete process.env.VERCEL_ENV;
42
+ });
43
+ (0, import_vitest.test)("verifies a token using the Vercel issuer and JWKS", async () => {
44
+ const result = await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
45
+ audience: "https://oidc.vercel.com/team1",
46
+ subject: "owner:team1:project:site:environment:production"
47
+ });
48
+ (0, import_vitest.expect)(result).toStrictEqual(verifyResult);
49
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledWith("token", mocks.remoteJwks, {
50
+ algorithms: ["RS256"],
51
+ audience: "https://oidc.vercel.com/team1",
52
+ subject: "owner:team1:project:site:environment:production"
53
+ });
54
+ });
55
+ (0, import_vitest.test)("verifies a token without additional options", async () => {
56
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token");
57
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledWith("token", mocks.remoteJwks, {
58
+ algorithms: ["RS256"]
59
+ });
60
+ });
61
+ (0, import_vitest.test)("reuses the Vercel remote JWKS resolver", async () => {
62
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("first-token");
63
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("second-token");
64
+ (0, import_vitest.expect)(mocks.createRemoteJWKSet).toHaveBeenCalledTimes(1);
65
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(2);
66
+ });
67
+ (0, import_vitest.test)("passes custom algorithms to Jose verification", async () => {
68
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
69
+ algorithms: ["RS512"]
70
+ });
71
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledWith("token", mocks.remoteJwks, {
72
+ algorithms: ["RS512"]
73
+ });
74
+ });
75
+ (0, import_vitest.test)("accepts a team issuer by default", async () => {
76
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token");
77
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
78
+ });
79
+ (0, import_vitest.test)("accepts the global issuer", async () => {
80
+ mockVerifiedPayload({
81
+ iss: "https://oidc.vercel.com"
82
+ });
83
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token");
84
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
85
+ });
86
+ (0, import_vitest.test)("accepts a matching explicit issuer option", async () => {
87
+ mockVerifiedPayload({
88
+ iss: "https://oidc.vercel.com/acme"
89
+ });
90
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
91
+ issuer: "https://oidc.vercel.com/acme"
92
+ });
93
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
94
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledWith("token", mocks.remoteJwks, {
95
+ algorithms: ["RS256"],
96
+ issuer: "https://oidc.vercel.com/acme"
97
+ });
98
+ });
99
+ (0, import_vitest.test)("uses VERCEL_ENV when VERCEL_TARGET_ENV is missing", async () => {
100
+ delete process.env.VERCEL_TARGET_ENV;
101
+ process.env.VERCEL_ENV = "production";
102
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token");
103
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
104
+ });
105
+ (0, import_vitest.test)("accepts custom projectId and environment options", async () => {
106
+ mockVerifiedPayload({
107
+ project_id: "prj_custom",
108
+ environment: "preview"
109
+ });
110
+ const result = await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
111
+ projectId: "prj_custom",
112
+ environment: "preview"
113
+ });
114
+ (0, import_vitest.expect)(result.payload.project_id).toBe("prj_custom");
115
+ (0, import_vitest.expect)(result.payload.environment).toBe("preview");
116
+ });
117
+ (0, import_vitest.test)("accepts a matching projectId array option", async () => {
118
+ mockVerifiedPayload({
119
+ project_id: "prj_allowed"
120
+ });
121
+ const result = await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
122
+ projectId: ["prj_first", "prj_allowed"]
123
+ });
124
+ (0, import_vitest.expect)(result.payload.project_id).toBe("prj_allowed");
125
+ });
126
+ (0, import_vitest.test)("accepts a matching ownerId option", async () => {
127
+ const result = await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
128
+ ownerId: "team_team1"
129
+ });
130
+ (0, import_vitest.expect)(result.payload.owner_id).toBe("team_team1");
131
+ });
132
+ (0, import_vitest.test)("does not require ownerId by default", async () => {
133
+ mockVerifiedPayload({
134
+ owner_id: "team_other"
135
+ });
136
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token");
137
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
138
+ });
139
+ (0, import_vitest.test)("allows any project_id claim with projectId wildcard", async () => {
140
+ mockVerifiedPayload({
141
+ project_id: "prj_other"
142
+ });
143
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
144
+ projectId: "*",
145
+ ownerId: "team_team1"
146
+ });
147
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
148
+ });
149
+ (0, import_vitest.test)("allows projectId wildcard with audience verification", async () => {
150
+ mockVerifiedPayload({
151
+ project_id: "prj_other"
152
+ });
153
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
154
+ projectId: "*",
155
+ audience: "https://oidc.vercel.com/team1"
156
+ });
157
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledWith("token", mocks.remoteJwks, {
158
+ algorithms: ["RS256"],
159
+ audience: "https://oidc.vercel.com/team1"
160
+ });
161
+ });
162
+ (0, import_vitest.test)("requires ownerId or audience when projectId wildcard is used", async () => {
163
+ await (0, import_vitest.expect)(
164
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
165
+ projectId: "*"
166
+ })
167
+ ).rejects.toThrow(
168
+ "Expected ownerId or audience to be provided when projectId is '*'."
169
+ );
170
+ (0, import_vitest.expect)(import_jose.jwtVerify).not.toHaveBeenCalled();
171
+ });
172
+ (0, import_vitest.test)("requires ownerId or a non-empty audience when projectId wildcard is used", async () => {
173
+ await (0, import_vitest.expect)(
174
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
175
+ projectId: "*",
176
+ audience: []
177
+ })
178
+ ).rejects.toThrow(
179
+ "Expected ownerId or audience to be provided when projectId is '*'."
180
+ );
181
+ (0, import_vitest.expect)(import_jose.jwtVerify).not.toHaveBeenCalled();
182
+ });
183
+ (0, import_vitest.test)("allows any environment claim with environment wildcard", async () => {
184
+ mockVerifiedPayload({
185
+ environment: "preview"
186
+ });
187
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
188
+ environment: "*"
189
+ });
190
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledTimes(1);
191
+ });
192
+ (0, import_vitest.test)("accepts a matching environment array option", async () => {
193
+ mockVerifiedPayload({
194
+ environment: "preview"
195
+ });
196
+ const result = await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
197
+ environment: ["production", "preview"]
198
+ });
199
+ (0, import_vitest.expect)(result.payload.environment).toBe("preview");
200
+ });
201
+ (0, import_vitest.test)("rejects a token from a different project", async () => {
202
+ mockVerifiedPayload({
203
+ project_id: "prj_other"
204
+ });
205
+ await (0, import_vitest.expect)((0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token")).rejects.toThrow(
206
+ 'Expected Vercel OIDC token project_id claim to be "prj_test".'
207
+ );
208
+ });
209
+ (0, import_vitest.test)("rejects a token from a project outside the projectId array", async () => {
210
+ mockVerifiedPayload({
211
+ project_id: "prj_other"
212
+ });
213
+ await (0, import_vitest.expect)(
214
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
215
+ projectId: ["prj_first", "prj_second"]
216
+ })
217
+ ).rejects.toThrow(
218
+ 'Expected Vercel OIDC token project_id claim to be one of: "prj_first", "prj_second".'
219
+ );
220
+ });
221
+ (0, import_vitest.test)("requires a non-empty projectId array", async () => {
222
+ await (0, import_vitest.expect)(
223
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
224
+ projectId: []
225
+ })
226
+ ).rejects.toThrow(
227
+ "Expected VERCEL_PROJECT_ID to be set or projectId to be provided. Pass projectId: '*' to allow any project_id claim."
228
+ );
229
+ });
230
+ (0, import_vitest.test)("rejects a token from a different owner", async () => {
231
+ mockVerifiedPayload({
232
+ owner_id: "team_other"
233
+ });
234
+ await (0, import_vitest.expect)(
235
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
236
+ ownerId: "team_team1"
237
+ })
238
+ ).rejects.toThrow(
239
+ 'Expected Vercel OIDC token owner_id claim to be "team_team1".'
240
+ );
241
+ });
242
+ (0, import_vitest.test)("rejects a token from a non-Vercel issuer", async () => {
243
+ mockVerifiedPayload({
244
+ iss: "https://example.com"
245
+ });
246
+ await (0, import_vitest.expect)((0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token")).rejects.toThrow(
247
+ 'Expected Vercel OIDC token iss claim to be "https://oidc.vercel.com" or to start with "https://oidc.vercel.com/".'
248
+ );
249
+ });
250
+ (0, import_vitest.test)("passes explicit issuer option to Jose verification", async () => {
251
+ await (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
252
+ issuer: "https://oidc.vercel.com/other-team"
253
+ });
254
+ (0, import_vitest.expect)(import_jose.jwtVerify).toHaveBeenCalledWith("token", mocks.remoteJwks, {
255
+ algorithms: ["RS256"],
256
+ issuer: "https://oidc.vercel.com/other-team"
257
+ });
258
+ });
259
+ (0, import_vitest.test)("rejects a token from an issuer that only shares the prefix text", async () => {
260
+ mockVerifiedPayload({
261
+ iss: "https://oidc.vercel.com.evil.example"
262
+ });
263
+ await (0, import_vitest.expect)((0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token")).rejects.toThrow(
264
+ 'Expected Vercel OIDC token iss claim to be "https://oidc.vercel.com" or to start with "https://oidc.vercel.com/".'
265
+ );
266
+ });
267
+ (0, import_vitest.test)("rejects a token from a different environment", async () => {
268
+ mockVerifiedPayload({
269
+ environment: "preview"
270
+ });
271
+ await (0, import_vitest.expect)((0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token")).rejects.toThrow(
272
+ 'Expected Vercel OIDC token environment claim to be "production".'
273
+ );
274
+ });
275
+ (0, import_vitest.test)("rejects a token from an environment outside the environment array", async () => {
276
+ mockVerifiedPayload({
277
+ environment: "preview"
278
+ });
279
+ await (0, import_vitest.expect)(
280
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
281
+ environment: ["production", "development"]
282
+ })
283
+ ).rejects.toThrow(
284
+ 'Expected Vercel OIDC token environment claim to be one of: "production", "development".'
285
+ );
286
+ });
287
+ (0, import_vitest.test)("requires a projectId default or wildcard", async () => {
288
+ delete process.env.VERCEL_PROJECT_ID;
289
+ await (0, import_vitest.expect)((0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token")).rejects.toThrow(
290
+ "Expected VERCEL_PROJECT_ID to be set or projectId to be provided. Pass projectId: '*' to allow any project_id claim."
291
+ );
292
+ });
293
+ (0, import_vitest.test)("requires an environment default or wildcard", async () => {
294
+ delete process.env.VERCEL_TARGET_ENV;
295
+ delete process.env.VERCEL_ENV;
296
+ await (0, import_vitest.expect)((0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token")).rejects.toThrow(
297
+ "Expected VERCEL_TARGET_ENV or VERCEL_ENV to be set or environment to be provided. Pass environment: '*' to allow any environment claim."
298
+ );
299
+ });
300
+ (0, import_vitest.test)("requires a non-empty environment array", async () => {
301
+ await (0, import_vitest.expect)(
302
+ (0, import_verify_vercel_oidc_token.verifyVercelOidcToken)("token", {
303
+ environment: []
304
+ })
305
+ ).rejects.toThrow(
306
+ "Expected VERCEL_TARGET_ENV or VERCEL_ENV to be set or environment to be provided. Pass environment: '*' to allow any environment claim."
307
+ );
308
+ });
309
+ function mockVerifiedPayload(payload) {
310
+ import_vitest.vi.mocked(import_jose.jwtVerify).mockResolvedValue({
311
+ ...verifyResult,
312
+ payload: {
313
+ ...verifyResult.payload,
314
+ ...payload
315
+ }
316
+ });
317
+ }
318
+ });
package/docs/README.md CHANGED
@@ -9,9 +9,14 @@
9
9
  - [AccessTokenMissingError](classes/AccessTokenMissingError.md)
10
10
  - [RefreshAccessTokenFailedError](classes/RefreshAccessTokenFailedError.md)
11
11
 
12
+ ## Type Aliases
13
+
14
+ - [VercelOidcPayload](type-aliases/VercelOidcPayload.md)
15
+
12
16
  ## Functions
13
17
 
14
18
  - [getContext](functions/getContext.md)
15
19
  - [getVercelOidcToken](functions/getVercelOidcToken.md)
16
20
  - [getVercelOidcTokenSync](functions/getVercelOidcTokenSync.md)
17
21
  - [getVercelToken](functions/getVercelToken.md)
22
+ - [verifyVercelOidcToken](functions/verifyVercelOidcToken.md)
@@ -0,0 +1,57 @@
1
+ [**@vercel/oidc**](../README.md)
2
+
3
+ ---
4
+
5
+ # Function: verifyVercelOidcToken()
6
+
7
+ > **verifyVercelOidcToken**\<`PayloadType`\>(`token`, `options?`): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`JWTVerifyResult`\<`PayloadType`\>\>
8
+
9
+ Defined in: [packages/oidc/src/verify-vercel-oidc-token.ts:53](https://github.com/vercel/vercel/blob/main/packages/oidc/src/verify-vercel-oidc-token.ts#L53)
10
+
11
+ Verifies a Vercel OIDC token against Vercel's remote JWKS.
12
+
13
+ The issuer must be `https://oidc.vercel.com` or start with
14
+ `https://oidc.vercel.com/`. The JWKS is always
15
+ `https://oidc.vercel.com/.well-known/jwks`.
16
+
17
+ Options:
18
+
19
+ - `issuer`: Expected `iss` claim verified by Jose. The verified issuer must
20
+ still be `https://oidc.vercel.com` or start with
21
+ `https://oidc.vercel.com/`.
22
+ - `projectId`: Expected `project_id` claim or claims. Defaults to
23
+ `process.env.VERCEL_PROJECT_ID`. Pass an array to allow any matching
24
+ project ID. Pass `'*'` to allow any project ID. When `projectId` is `'*'`,
25
+ either `ownerId` or `audience` is required.
26
+ - `environment`: Expected `environment` claim or claims. Defaults to
27
+ `process.env.VERCEL_TARGET_ENV || process.env.VERCEL_ENV`. Pass an array
28
+ to allow any matching environment. Pass `'*'` to allow any environment.
29
+ - `ownerId`: Expected `owner_id` claim. When omitted, the claim is not
30
+ checked.
31
+ - Any other Jose JWT verification option.
32
+
33
+ ## Type Parameters
34
+
35
+ ### PayloadType
36
+
37
+ `PayloadType` = [`VercelOidcPayload`](../type-aliases/VercelOidcPayload.md)
38
+
39
+ ## Parameters
40
+
41
+ ### token
42
+
43
+ `string`
44
+
45
+ The Vercel OIDC token to verify.
46
+
47
+ ### options?
48
+
49
+ `object` & `JWTVerifyOptions`
50
+
51
+ Optional Jose JWT verification options.
52
+
53
+ ## Returns
54
+
55
+ [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`JWTVerifyResult`\<`PayloadType`\>\>
56
+
57
+ Jose's verified JWT result.
@@ -0,0 +1,39 @@
1
+ [**@vercel/oidc**](../README.md)
2
+
3
+ ---
4
+
5
+ # Type Alias: VercelOidcPayload
6
+
7
+ > **VercelOidcPayload** = `JWTPayload` & `object`
8
+
9
+ Defined in: [packages/oidc/src/verify-vercel-oidc-token.ts:16](https://github.com/vercel/vercel/blob/main/packages/oidc/src/verify-vercel-oidc-token.ts#L16)
10
+
11
+ ## Type Declaration
12
+
13
+ ### aud
14
+
15
+ > **aud**: `string`
16
+
17
+ ### environment
18
+
19
+ > **environment**: `string`
20
+
21
+ ### external_sub?
22
+
23
+ > `optional` **external_sub?**: `string`
24
+
25
+ ### iss
26
+
27
+ > **iss**: `string`
28
+
29
+ ### owner_id
30
+
31
+ > **owner_id**: `string`
32
+
33
+ ### project_id
34
+
35
+ > **project_id**: `string`
36
+
37
+ ### sub
38
+
39
+ > **sub**: `string`
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "require": "./dist/index.js"
19
19
  }
20
20
  },
21
- "version": "3.6.0",
21
+ "version": "3.6.2",
22
22
  "repository": {
23
23
  "directory": "packages/oidc",
24
24
  "type": "git",
@@ -28,8 +28,9 @@
28
28
  "url": "https://github.com/vercel/vercel/issues"
29
29
  },
30
30
  "dependencies": {
31
+ "jose": "^5.9.6",
31
32
  "@vercel/cli-config": "0.2.0",
32
- "@vercel/cli-exec": "0.1.1"
33
+ "@vercel/cli-exec": "1.0.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "prettier": "3.8.3",