@vercel/oidc 3.5.0 → 3.6.1

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.
@@ -26,9 +26,25 @@ var import_vitest = require("vitest");
26
26
  var os = __toESM(require("os"));
27
27
  var path = __toESM(require("path"));
28
28
  var fs = __toESM(require("fs"));
29
+ var import_cli_config = require("@vercel/cli-config");
30
+ var import_cli_exec = require("@vercel/cli-exec");
29
31
  var import_token_io = require("./token-io");
30
32
  var tokenUtil = __toESM(require("./token-util"));
31
33
  var import_token = require("./token");
34
+ import_vitest.vi.mock("@vercel/cli-config", () => ({
35
+ getGlobalPathConfig: import_vitest.vi.fn(),
36
+ getLikelyEffectiveCredStorage: import_vitest.vi.fn(() => "file")
37
+ }));
38
+ import_vitest.vi.mock("@vercel/cli-exec", () => ({
39
+ execVercelCli: import_vitest.vi.fn(),
40
+ VercelCliError: class VercelCliError extends Error {
41
+ constructor(options) {
42
+ super(options.message);
43
+ this.name = "VercelCliError";
44
+ this.stderr = options.stderr;
45
+ }
46
+ }
47
+ }));
32
48
  import_vitest.vi.mock("./token-io");
33
49
  (0, import_vitest.describe)("refreshToken", () => {
34
50
  let rootDir;
@@ -38,6 +54,7 @@ import_vitest.vi.mock("./token-io");
38
54
  const projectId = "prj_test123";
39
55
  (0, import_vitest.beforeEach)(() => {
40
56
  import_vitest.vi.clearAllMocks();
57
+ import_vitest.vi.mocked(import_cli_config.getLikelyEffectiveCredStorage).mockReturnValue("file");
41
58
  process.env.VERCEL_OIDC_TOKEN = void 0;
42
59
  const random = `test-${(0, import_crypto.randomUUID)()}`;
43
60
  rootDir = path.join(os.tmpdir(), random);
@@ -49,7 +66,10 @@ import_vitest.vi.mock("./token-io");
49
66
  fs.mkdirSync(path.join(rootDir, ".vercel"), {
50
67
  recursive: true
51
68
  });
52
- fs.writeFileSync(path.join(cliDataDir, "auth.json"), '{token: "test"}');
69
+ fs.writeFileSync(
70
+ path.join(cliDataDir, "auth.json"),
71
+ JSON.stringify({ token: "test" })
72
+ );
53
73
  fs.writeFileSync(
54
74
  path.join(rootDir, ".vercel", "project.json"),
55
75
  JSON.stringify({ projectId })
@@ -57,6 +77,7 @@ import_vitest.vi.mock("./token-io");
57
77
  import_vitest.vi.spyOn(process, "cwd").mockReturnValue(rootDir);
58
78
  import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(rootDir);
59
79
  import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(userDataDir);
80
+ import_vitest.vi.mocked(import_cli_config.getGlobalPathConfig).mockReturnValue(cliDataDir);
60
81
  import_vitest.vi.spyOn(tokenUtil, "getVercelToken").mockResolvedValue("test");
61
82
  import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({
62
83
  token: "test-token"
@@ -66,6 +87,15 @@ import_vitest.vi.mock("./token-io");
66
87
  name: "test-name",
67
88
  exp: Date.now() + 1e5
68
89
  });
90
+ import_vitest.vi.mocked(import_cli_exec.execVercelCli).mockResolvedValue({
91
+ stdout: JSON.stringify({ token: "cli-token" }),
92
+ stderr: "",
93
+ invocation: {
94
+ command: "vercel",
95
+ commandArgs: [],
96
+ source: "path"
97
+ }
98
+ });
69
99
  });
70
100
  (0, import_vitest.test)("should correctly load saved token from file", async () => {
71
101
  const token = { token: "test-saved" };
@@ -81,6 +111,66 @@ import_vitest.vi.mock("./token-io");
81
111
  const token = JSON.parse(fs.readFileSync(tokenPath, "utf8"));
82
112
  (0, import_vitest.expect)(token).toEqual({ token: "test-token" });
83
113
  });
114
+ (0, import_vitest.test)("should use cli-exec when keyring-backed credentials are likely", async () => {
115
+ import_vitest.vi.mocked(import_cli_config.getLikelyEffectiveCredStorage).mockReturnValue("keyring");
116
+ await (0, import_token.refreshToken)();
117
+ (0, import_vitest.expect)(import_cli_exec.execVercelCli).toHaveBeenCalledWith([
118
+ "project",
119
+ "token",
120
+ projectId,
121
+ "--format=json"
122
+ ]);
123
+ (0, import_vitest.expect)(tokenUtil.getVercelToken).not.toHaveBeenCalled();
124
+ (0, import_vitest.expect)(tokenUtil.getVercelOidcToken).not.toHaveBeenCalled();
125
+ (0, import_vitest.expect)(process.env.VERCEL_OIDC_TOKEN).toBe("cli-token");
126
+ });
127
+ (0, import_vitest.test)("should pass scope to cli-exec when team is known", async () => {
128
+ const customTeamId = "team_customscope123";
129
+ import_vitest.vi.mocked(import_cli_config.getLikelyEffectiveCredStorage).mockReturnValue("keyring");
130
+ await (0, import_token.refreshToken)({ team: customTeamId, project: projectId });
131
+ (0, import_vitest.expect)(import_cli_exec.execVercelCli).toHaveBeenCalledWith([
132
+ "project",
133
+ "token",
134
+ projectId,
135
+ "--format=json",
136
+ "--scope",
137
+ customTeamId
138
+ ]);
139
+ });
140
+ (0, import_vitest.test)("should fail with a cli-specific error for malformed cli json output", async () => {
141
+ import_vitest.vi.mocked(import_cli_config.getLikelyEffectiveCredStorage).mockReturnValue("keyring");
142
+ import_vitest.vi.mocked(import_cli_exec.execVercelCli).mockResolvedValue({
143
+ stdout: JSON.stringify({ nope: "missing-token" }),
144
+ stderr: "",
145
+ invocation: {
146
+ command: "vercel",
147
+ commandArgs: [],
148
+ source: "path"
149
+ }
150
+ });
151
+ await (0, import_vitest.expect)((0, import_token.refreshToken)()).rejects.toThrow(
152
+ "Failed to refresh OIDC token with the Vercel CLI: Vercel OIDC token is malformed. Expected a string-valued token property."
153
+ );
154
+ });
155
+ (0, import_vitest.test)("should include cli stderr when cli-exec fails", async () => {
156
+ import_vitest.vi.mocked(import_cli_config.getLikelyEffectiveCredStorage).mockReturnValue("keyring");
157
+ import_vitest.vi.mocked(import_cli_exec.execVercelCli).mockRejectedValue(
158
+ new import_cli_exec.VercelCliError({
159
+ code: "VERCEL_CLI_ERRORED",
160
+ message: "Command failed with exit code 1",
161
+ stderr: "Project not found for this scope",
162
+ exitCode: 1,
163
+ invocation: {
164
+ command: "vercel",
165
+ commandArgs: [],
166
+ source: "path"
167
+ }
168
+ })
169
+ );
170
+ await (0, import_vitest.expect)((0, import_token.refreshToken)()).rejects.toThrow(
171
+ "Failed to refresh OIDC token with the Vercel CLI: Command failed with exit code 1\nProject not found for this scope"
172
+ );
173
+ });
84
174
  (0, import_vitest.test)("should use provided team and project instead of reading project.json", async () => {
85
175
  const customProjectId = "prj_custom123";
86
176
  const customTeamId = "team_custom456";
@@ -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)
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **getVercelToken**(`options?`): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`string`\>
8
8
 
9
- Defined in: [packages/oidc/src/token-util.ts:35](https://github.com/vercel/vercel/blob/main/packages/oidc/src/token-util.ts#L35)
9
+ Defined in: [packages/oidc/src/token-util.ts:27](https://github.com/vercel/vercel/blob/main/packages/oidc/src/token-util.ts#L27)
10
10
 
11
11
  ## Parameters
12
12