@vercel/oidc 3.4.1 → 3.6.0

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,24 @@
1
1
  # @vercel/oidc
2
2
 
3
+ ## 3.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fddeb55: Add configurable credentials storage handling across the CLI auth stack. Storage of credentials can be configured by the new `credStorage` key in global `config.json` or the new `VERCEL_TOKEN_STORAGE` environment variable. The environment variable takes precedence over the configuration key. Accepted values are `file` (store credentials in `auth.json`), `keyring` (store credentials in system keyring, e.g macOS Keychain or Secrets Service on Linux), and `auto` (try storing in keyring if available, fall back to `file` if keyring is not available).
8
+
9
+ `@vercel/oidc` supports keyring-stored authentication credentials by delegating the OIDC minting to the CLI executable via `@vercel/cli-exec`.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [fddeb55]
14
+ - @vercel/cli-config@0.2.0
15
+
16
+ ## 3.5.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 5a700dc: Add conditional edge-light export to support Edge Runtime
21
+
3
22
  ## 3.4.1
4
23
 
5
24
  ### Patch Changes
@@ -32,8 +32,11 @@ class AccessTokenMissingError extends Error {
32
32
  }
33
33
  class RefreshAccessTokenFailedError extends Error {
34
34
  constructor(cause) {
35
- super("Failed to refresh authentication token.", { cause });
35
+ super("Failed to refresh authentication token.");
36
36
  this.name = "RefreshAccessTokenFailedError";
37
+ if (cause !== void 0) {
38
+ this.cause = cause;
39
+ }
37
40
  }
38
41
  }
39
42
  // Annotate the CommonJS export names for ESM import in node:
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Gets the current OIDC token from the request context or the environment variable.
3
+ *
4
+ * Do not cache this value, as it is subject to change in production!
5
+ *
6
+ * This function is used to retrieve the OIDC token from the request context or the environment variable.
7
+ * It checks for the `x-vercel-oidc-token` header in the request context and falls back to the `VERCEL_OIDC_TOKEN` environment variable if the header is not present.
8
+ *
9
+ * This function will not refresh the token if it is expired. For refreshing the token, use the @{link getVercelOidcToken} function.
10
+ *
11
+ * @returns {string} The OIDC token.
12
+ * @throws {Error} If the `x-vercel-oidc-token` header is missing from the request context and the environment variable `VERCEL_OIDC_TOKEN` is not set.
13
+ *
14
+ * @example
15
+ *
16
+ * ```js
17
+ * // Using the OIDC token
18
+ * const token = getVercelOidcTokenSync();
19
+ * console.log('OIDC Token:', token);
20
+ * ```
21
+ */
22
+ export declare function getVercelOidcTokenSync(): string;
@@ -0,0 +1,37 @@
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 get_vercel_oidc_token_sync_exports = {};
20
+ __export(get_vercel_oidc_token_sync_exports, {
21
+ getVercelOidcTokenSync: () => getVercelOidcTokenSync
22
+ });
23
+ module.exports = __toCommonJS(get_vercel_oidc_token_sync_exports);
24
+ var import_get_context = require("./get-context");
25
+ function getVercelOidcTokenSync() {
26
+ const token = (0, import_get_context.getContext)().headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
27
+ if (!token) {
28
+ throw new Error(
29
+ `The 'x-vercel-oidc-token' header is missing from the request.`
30
+ );
31
+ }
32
+ return token;
33
+ }
34
+ // Annotate the CommonJS export names for ESM import in node:
35
+ 0 && (module.exports = {
36
+ getVercelOidcTokenSync
37
+ });
@@ -58,25 +58,3 @@ export interface GetVercelOidcTokenOptions {
58
58
  * ```
59
59
  */
60
60
  export declare function getVercelOidcToken(options?: GetVercelOidcTokenOptions): Promise<string>;
61
- /**
62
- * Gets the current OIDC token from the request context or the environment variable.
63
- *
64
- * Do not cache this value, as it is subject to change in production!
65
- *
66
- * This function is used to retrieve the OIDC token from the request context or the environment variable.
67
- * It checks for the `x-vercel-oidc-token` header in the request context and falls back to the `VERCEL_OIDC_TOKEN` environment variable if the header is not present.
68
- *
69
- * This function will not refresh the token if it is expired. For refreshing the token, use the @{link getVercelOidcToken} function.
70
- *
71
- * @returns {string} The OIDC token.
72
- * @throws {Error} If the `x-vercel-oidc-token` header is missing from the request context and the environment variable `VERCEL_OIDC_TOKEN` is not set.
73
- *
74
- * @example
75
- *
76
- * ```js
77
- * // Using the OIDC token
78
- * const token = getVercelOidcTokenSync();
79
- * console.log('OIDC Token:', token);
80
- * ```
81
- */
82
- export declare function getVercelOidcTokenSync(): string;
@@ -26,19 +26,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var get_vercel_oidc_token_exports = {};
30
- __export(get_vercel_oidc_token_exports, {
31
- getVercelOidcToken: () => getVercelOidcToken,
32
- getVercelOidcTokenSync: () => getVercelOidcTokenSync
29
+ var get_vercel_oidc_token_with_refresh_exports = {};
30
+ __export(get_vercel_oidc_token_with_refresh_exports, {
31
+ getVercelOidcToken: () => getVercelOidcToken
33
32
  });
34
- module.exports = __toCommonJS(get_vercel_oidc_token_exports);
35
- var import_get_context = require("./get-context");
33
+ module.exports = __toCommonJS(get_vercel_oidc_token_with_refresh_exports);
34
+ var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync");
36
35
  var import_token_error = require("./token-error");
37
36
  async function getVercelOidcToken(options) {
38
37
  let token = "";
39
38
  let err;
40
39
  try {
41
- token = getVercelOidcTokenSync();
40
+ token = (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)();
42
41
  } catch (error) {
43
42
  err = error;
44
43
  }
@@ -49,7 +48,7 @@ async function getVercelOidcToken(options) {
49
48
  ]);
50
49
  if (!token || isExpired(getTokenPayload(token), options?.expirationBufferMs)) {
51
50
  await refreshToken(options);
52
- token = getVercelOidcTokenSync();
51
+ token = (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)();
53
52
  }
54
53
  } catch (error) {
55
54
  let message = err instanceof Error ? err.message : "";
@@ -64,17 +63,7 @@ ${error.message}`;
64
63
  }
65
64
  return token;
66
65
  }
67
- function getVercelOidcTokenSync() {
68
- const token = (0, import_get_context.getContext)().headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
69
- if (!token) {
70
- throw new Error(
71
- `The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`
72
- );
73
- }
74
- return token;
75
- }
76
66
  // Annotate the CommonJS export names for ESM import in node:
77
67
  0 && (module.exports = {
78
- getVercelOidcToken,
79
- getVercelOidcTokenSync
68
+ getVercelOidcToken
80
69
  });
@@ -27,8 +27,19 @@ var os = __toESM(require("os"));
27
27
  var path = __toESM(require("path"));
28
28
  var fs = __toESM(require("fs"));
29
29
  var import_token_io = require("./token-io");
30
- var import_get_vercel_oidc_token = require("./get-vercel-oidc-token");
30
+ var import_cli_config = require("@vercel/cli-config");
31
+ var import_get_vercel_oidc_token_with_refresh = require("./get-vercel-oidc-token-with-refresh");
31
32
  var tokenUtil = __toESM(require("./token-util"));
33
+ import_vitest.vi.mock("@vercel/cli-config", async (importOriginal) => ({
34
+ ...await importOriginal(),
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
+ }
42
+ }));
32
43
  import_vitest.vi.mock("./token-io");
33
44
  import_vitest.vi.mock("./get-context", () => ({
34
45
  getContext: () => ({ headers: {} })
@@ -57,6 +68,7 @@ import_vitest.vi.mock("./get-context", () => ({
57
68
  import_vitest.vi.spyOn(process, "cwd").mockReturnValue(rootDir);
58
69
  import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(rootDir);
59
70
  import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(userDataDir);
71
+ import_vitest.vi.mocked(import_cli_config.getGlobalPathConfig).mockReturnValue(cliDataDir);
60
72
  });
61
73
  (0, import_vitest.afterEach)(() => {
62
74
  if (fs.existsSync(rootDir)) {
@@ -69,7 +81,7 @@ import_vitest.vi.mock("./get-context", () => ({
69
81
  JSON.stringify({ projectId, orgId: teamId })
70
82
  );
71
83
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
72
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
84
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
73
85
  /No authentication found/
74
86
  );
75
87
  });
@@ -79,14 +91,14 @@ import_vitest.vi.mock("./get-context", () => ({
79
91
  JSON.stringify({ token: "test-auth-token" })
80
92
  );
81
93
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
82
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
94
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
83
95
  /project\.json not found, have you linked your project with `vc link\?`/
84
96
  );
85
97
  });
86
98
  (0, import_vitest.test)("should throw helpful error when root directory cannot be found", async () => {
87
99
  import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(null);
88
100
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
89
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
101
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
90
102
  /Unable to find project root directory\. Have you linked your project with `vc link\?`/
91
103
  );
92
104
  });
@@ -97,7 +109,7 @@ import_vitest.vi.mock("./get-context", () => ({
97
109
  );
98
110
  import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(null);
99
111
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
100
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
112
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
101
113
  /Unable to find user data directory\. Please reach out to Vercel support\./
102
114
  );
103
115
  });
@@ -114,7 +126,7 @@ import_vitest.vi.mock("./get-context", () => ({
114
126
  new Error("Failed to refresh OIDC token: Unauthorized")
115
127
  );
116
128
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
117
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
129
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
118
130
  /Failed to refresh OIDC token: Unauthorized/
119
131
  );
120
132
  });
@@ -129,32 +141,28 @@ import_vitest.vi.mock("./get-context", () => ({
129
141
  );
130
142
  import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockRejectedValue(
131
143
  new TypeError(
132
- "Vercel OIDC token is malformed. Expected a string-valued token property. Please run `vc env pull` and try again"
144
+ "Vercel OIDC token is malformed. Expected a string-valued token property."
133
145
  )
134
146
  );
135
147
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
136
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
137
- /Vercel OIDC token is malformed\. Expected a string-valued token property\. Please run `vc env pull` and try again/
148
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
149
+ /Vercel OIDC token is malformed\. Expected a string-valued token property\./
138
150
  );
139
151
  });
140
152
  (0, import_vitest.test)("should throw helpful error when token has invalid format", async () => {
141
153
  process.env.VERCEL_OIDC_TOKEN = "not-a-valid-jwt-token";
142
154
  import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockImplementation(() => {
143
- throw new Error("Invalid token. Please run `vc env pull` and try again");
155
+ throw new Error("Invalid token.");
144
156
  });
145
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
146
- /Invalid token\. Please run `vc env pull` and try again/
147
- );
157
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(/Invalid token\./);
148
158
  });
149
159
  (0, import_vitest.test)("should throw error when token expiry check fails", async () => {
150
160
  process.env.VERCEL_OIDC_TOKEN = "not-a-jwt-token";
151
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(
152
- /Invalid token\. Please run `vc env pull` and try again/
153
- );
161
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(/Invalid token\./);
154
162
  });
155
163
  (0, import_vitest.test)("should fail when no token exists and no CLI credentials available", async () => {
156
164
  process.env.VERCEL_OIDC_TOKEN = void 0;
157
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(/Invalid token/);
165
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(/Invalid token/);
158
166
  });
159
167
  (0, import_vitest.test)("should propagate filesystem errors when saving token fails", async () => {
160
168
  fs.writeFileSync(
@@ -177,7 +185,7 @@ import_vitest.vi.mock("./get-context", () => ({
177
185
  throw new Error("EACCES: permission denied");
178
186
  });
179
187
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
180
- await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(/EACCES|permission/i);
188
+ await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(/EACCES|permission/i);
181
189
  });
182
190
  (0, import_vitest.test)("should succeed when valid token exists in env", async () => {
183
191
  const validToken = createValidToken();
@@ -187,7 +195,7 @@ import_vitest.vi.mock("./get-context", () => ({
187
195
  name: "test-name",
188
196
  exp: Date.now() / 1e3 + 43200
189
197
  });
190
- const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)();
198
+ const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)();
191
199
  (0, import_vitest.expect)(token).toBe(validToken);
192
200
  });
193
201
  (0, import_vitest.test)("should refresh when token is expired but all configs are valid", async () => {
@@ -213,7 +221,7 @@ import_vitest.vi.mock("./get-context", () => ({
213
221
  exp: Date.now() / 1e3 + 43200
214
222
  });
215
223
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
216
- const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)();
224
+ const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)();
217
225
  (0, import_vitest.expect)(token).toBe(newToken);
218
226
  });
219
227
  (0, import_vitest.test)("should use provided team and project for refresh instead of reading project.json", async () => {
@@ -237,7 +245,7 @@ import_vitest.vi.mock("./get-context", () => ({
237
245
  exp: Date.now() / 1e3 + 43200
238
246
  });
239
247
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
240
- const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({
248
+ const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
241
249
  team: customTeamId,
242
250
  project: customProjectId
243
251
  });
@@ -272,7 +280,7 @@ import_vitest.vi.mock("./get-context", () => ({
272
280
  exp: Date.now() / 1e3 + 43200
273
281
  });
274
282
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
275
- const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({
283
+ const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
276
284
  project: customProjectId
277
285
  });
278
286
  (0, import_vitest.expect)(token).toBe(newToken);
@@ -306,7 +314,7 @@ import_vitest.vi.mock("./get-context", () => ({
306
314
  exp: Date.now() / 1e3 + 43200
307
315
  });
308
316
  process.env.VERCEL_OIDC_TOKEN = createExpiredToken();
309
- const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({
317
+ const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
310
318
  team: customTeamId
311
319
  });
312
320
  (0, import_vitest.expect)(token).toBe(newToken);
@@ -327,7 +335,7 @@ import_vitest.vi.mock("./get-context", () => ({
327
335
  name: "test-name",
328
336
  exp: Date.now() / 1e3 + 43200
329
337
  });
330
- const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({
338
+ const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
331
339
  team: "custom-team",
332
340
  project: "custom-project"
333
341
  });
@@ -24,6 +24,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  var import_vitest = require("vitest");
25
25
  var DefaultImports = __toESM(require("./index"));
26
26
  var BrowserImports = __toESM(require("./index-browser"));
27
+ import_vitest.vi.mock("@vercel/cli-exec", () => ({
28
+ execVercelCli: import_vitest.vi.fn(),
29
+ VercelCliError: class VercelCliError extends Error {
30
+ }
31
+ }));
27
32
  (0, import_vitest.describe)("browser export", () => {
28
33
  (0, import_vitest.test)("should match the default export", async () => {
29
34
  (0, import_vitest.expect)(Object.keys(BrowserImports).sort()).toStrictEqual(
@@ -0,0 +1,11 @@
1
+ export { getContext } from './get-context';
2
+ export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors';
3
+ export { getVercelOidcTokenSync } from './get-vercel-oidc-token-sync';
4
+ /**
5
+ * Gets the current OIDC token in Edge Runtime.
6
+ *
7
+ * Edge Runtime does not support automatic token refresh, so this returns the
8
+ * request-scoped token without checking expiration.
9
+ */
10
+ export declare function getVercelOidcToken(): Promise<string>;
11
+ export declare function getVercelToken(): Promise<string>;
@@ -0,0 +1,47 @@
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 index_edge_light_exports = {};
20
+ __export(index_edge_light_exports, {
21
+ AccessTokenMissingError: () => import_auth_errors.AccessTokenMissingError,
22
+ RefreshAccessTokenFailedError: () => import_auth_errors.RefreshAccessTokenFailedError,
23
+ getContext: () => import_get_context.getContext,
24
+ getVercelOidcToken: () => getVercelOidcToken,
25
+ getVercelOidcTokenSync: () => import_get_vercel_oidc_token_sync2.getVercelOidcTokenSync,
26
+ getVercelToken: () => getVercelToken
27
+ });
28
+ module.exports = __toCommonJS(index_edge_light_exports);
29
+ var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync");
30
+ var import_get_context = require("./get-context");
31
+ var import_auth_errors = require("./auth-errors");
32
+ var import_get_vercel_oidc_token_sync2 = require("./get-vercel-oidc-token-sync");
33
+ async function getVercelOidcToken() {
34
+ return (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)();
35
+ }
36
+ async function getVercelToken() {
37
+ throw new Error("getVercelToken is not supported in Edge Runtime");
38
+ }
39
+ // Annotate the CommonJS export names for ESM import in node:
40
+ 0 && (module.exports = {
41
+ AccessTokenMissingError,
42
+ RefreshAccessTokenFailedError,
43
+ getContext,
44
+ getVercelOidcToken,
45
+ getVercelOidcTokenSync,
46
+ getVercelToken
47
+ });
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { getVercelOidcToken, getVercelOidcTokenSync, } from './get-vercel-oidc-token';
1
+ export { getVercelOidcToken } from './get-vercel-oidc-token-with-refresh';
2
+ export { getVercelOidcTokenSync } from './get-vercel-oidc-token-sync';
2
3
  export { getContext } from './get-context';
3
4
  export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors';
4
5
  export { getVercelToken } from './token-util';
package/dist/index.js CHANGED
@@ -21,12 +21,13 @@ __export(src_exports, {
21
21
  AccessTokenMissingError: () => import_auth_errors.AccessTokenMissingError,
22
22
  RefreshAccessTokenFailedError: () => import_auth_errors.RefreshAccessTokenFailedError,
23
23
  getContext: () => import_get_context.getContext,
24
- getVercelOidcToken: () => import_get_vercel_oidc_token.getVercelOidcToken,
25
- getVercelOidcTokenSync: () => import_get_vercel_oidc_token.getVercelOidcTokenSync,
24
+ getVercelOidcToken: () => import_get_vercel_oidc_token_with_refresh.getVercelOidcToken,
25
+ getVercelOidcTokenSync: () => import_get_vercel_oidc_token_sync.getVercelOidcTokenSync,
26
26
  getVercelToken: () => import_token_util.getVercelToken
27
27
  });
28
28
  module.exports = __toCommonJS(src_exports);
29
- var import_get_vercel_oidc_token = require("./get-vercel-oidc-token");
29
+ var import_get_vercel_oidc_token_with_refresh = require("./get-vercel-oidc-token-with-refresh");
30
+ var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync");
30
31
  var import_get_context = require("./get-context");
31
32
  var import_auth_errors = require("./auth-errors");
32
33
  var import_token_util = require("./token-util");
@@ -1,4 +1,3 @@
1
- export declare function getVercelDataDir(): string | null;
2
1
  export interface GetVercelTokenOptions {
3
2
  /**
4
3
  * Optional time buffer in milliseconds before token expiry to consider it expired.
@@ -11,6 +10,7 @@ export declare function getVercelToken(options?: GetVercelTokenOptions): Promise
11
10
  interface VercelTokenResponse {
12
11
  token: string;
13
12
  }
13
+ export declare function getVercelOidcTokenFromCli(projectId: string, teamId?: string): Promise<VercelTokenResponse>;
14
14
  export declare function getVercelOidcToken(authToken: string, projectId: string, teamId?: string): Promise<VercelTokenResponse | null>;
15
15
  export declare function assertVercelOidcTokenResponse(res: unknown): asserts res is VercelTokenResponse;
16
16
  export declare function findProjectInfo(): {
@@ -31,8 +31,8 @@ __export(token_util_exports, {
31
31
  assertVercelOidcTokenResponse: () => assertVercelOidcTokenResponse,
32
32
  findProjectInfo: () => findProjectInfo,
33
33
  getTokenPayload: () => getTokenPayload,
34
- getVercelDataDir: () => getVercelDataDir,
35
34
  getVercelOidcToken: () => getVercelOidcToken,
35
+ getVercelOidcTokenFromCli: () => getVercelOidcTokenFromCli,
36
36
  getVercelToken: () => getVercelToken,
37
37
  isExpired: () => isExpired,
38
38
  loadToken: () => loadToken,
@@ -41,29 +41,23 @@ __export(token_util_exports, {
41
41
  module.exports = __toCommonJS(token_util_exports);
42
42
  var path = __toESM(require("path"));
43
43
  var fs = __toESM(require("fs"));
44
+ var import_cli_exec = require("@vercel/cli-exec");
45
+ var import_cli_config = require("@vercel/cli-config");
44
46
  var import_token_error = require("./token-error");
45
47
  var import_token_io = require("./token-io");
46
- var import_auth_config = require("./auth-config");
47
48
  var import_oauth = require("./oauth");
48
49
  var import_auth_errors = require("./auth-errors");
49
- function getVercelDataDir() {
50
- const vercelFolder = "com.vercel.cli";
51
- const dataDir = (0, import_token_io.getUserDataDir)();
52
- if (!dataDir) {
53
- return null;
54
- }
55
- return path.join(dataDir, vercelFolder);
56
- }
57
50
  async function getVercelToken(options) {
58
- const authConfig = (0, import_auth_config.readAuthConfig)();
59
- if (!authConfig?.token) {
51
+ const configDir = (0, import_cli_config.getGlobalPathConfig)();
52
+ const authConfig = (0, import_cli_config.tryReadAuthConfig)(configDir);
53
+ if (!authConfig || !authConfig.token && !authConfig.refreshToken) {
60
54
  throw new import_auth_errors.AccessTokenMissingError();
61
55
  }
62
- if ((0, import_auth_config.isValidAccessToken)(authConfig, options?.expirationBufferMs)) {
56
+ if (isValidAccessToken(authConfig, options?.expirationBufferMs)) {
63
57
  return authConfig.token;
64
58
  }
65
59
  if (!authConfig.refreshToken) {
66
- (0, import_auth_config.writeAuthConfig)({});
60
+ (0, import_cli_config.writeAuthConfig)(configDir, {});
67
61
  throw new import_auth_errors.RefreshAccessTokenFailedError("No refresh token available");
68
62
  }
69
63
  try {
@@ -72,26 +66,70 @@ async function getVercelToken(options) {
72
66
  });
73
67
  const [tokensError, tokens] = await (0, import_oauth.processTokenResponse)(tokenResponse);
74
68
  if (tokensError || !tokens) {
75
- (0, import_auth_config.writeAuthConfig)({});
69
+ (0, import_cli_config.writeAuthConfig)(configDir, {});
76
70
  throw new import_auth_errors.RefreshAccessTokenFailedError(tokensError);
77
71
  }
78
72
  const updatedConfig = {
79
73
  token: tokens.access_token,
80
- expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
74
+ expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in,
75
+ refreshToken: tokens.refresh_token
81
76
  };
82
- if (tokens.refresh_token) {
83
- updatedConfig.refreshToken = tokens.refresh_token;
84
- }
85
- (0, import_auth_config.writeAuthConfig)(updatedConfig);
77
+ (0, import_cli_config.writeAuthConfig)(configDir, updatedConfig);
86
78
  return updatedConfig.token;
87
79
  } catch (error) {
88
- (0, import_auth_config.writeAuthConfig)({});
80
+ (0, import_cli_config.writeAuthConfig)(configDir, {});
89
81
  if (error instanceof import_auth_errors.AccessTokenMissingError || error instanceof import_auth_errors.RefreshAccessTokenFailedError) {
90
82
  throw error;
91
83
  }
92
84
  throw new import_auth_errors.RefreshAccessTokenFailedError(error);
93
85
  }
94
86
  }
87
+ function isValidAccessToken(authConfig, expirationBufferMs = 0) {
88
+ if (!authConfig.token)
89
+ return false;
90
+ if (typeof authConfig.expiresAt !== "number")
91
+ return true;
92
+ const nowInSeconds = Math.floor(Date.now() / 1e3);
93
+ const bufferInSeconds = expirationBufferMs / 1e3;
94
+ return authConfig.expiresAt >= nowInSeconds + bufferInSeconds;
95
+ }
96
+ async function getVercelOidcTokenFromCli(projectId, teamId) {
97
+ const args = ["project", "token", projectId, "--format=json"];
98
+ if (teamId) {
99
+ args.push("--scope", teamId);
100
+ }
101
+ try {
102
+ const { stdout } = await (0, import_cli_exec.execVercelCli)(args);
103
+ let parsedOutput;
104
+ if (typeof stdout !== "string") {
105
+ throw new import_token_error.VercelOidcTokenError(
106
+ "Failed to refresh OIDC token: `vercel project token` did not return stdout"
107
+ );
108
+ }
109
+ try {
110
+ parsedOutput = JSON.parse(stdout);
111
+ } catch {
112
+ throw new import_token_error.VercelOidcTokenError(
113
+ "Failed to refresh OIDC token: `vercel project token` returned invalid JSON: " + stdout
114
+ );
115
+ }
116
+ assertVercelOidcTokenResponse(parsedOutput);
117
+ return parsedOutput;
118
+ } catch (error) {
119
+ if (error instanceof import_token_error.VercelOidcTokenError) {
120
+ throw error;
121
+ }
122
+ let message = error instanceof Error ? error.message : "";
123
+ const stderr = error instanceof import_cli_exec.VercelCliError ? error.stderr?.trim() : void 0;
124
+ if (stderr && !message.includes(stderr)) {
125
+ message = `${message}
126
+ ${stderr}`.trim();
127
+ }
128
+ throw new import_token_error.VercelOidcTokenError(
129
+ message ? `Failed to refresh OIDC token with the Vercel CLI: ${message}` : "Failed to refresh OIDC token with the Vercel CLI"
130
+ );
131
+ }
132
+ }
95
133
  async function getVercelOidcToken(authToken, projectId, teamId) {
96
134
  const url = `https://api.vercel.com/v1/projects/${projectId}/token?source=vercel-oidc-refresh${teamId ? `&teamId=${teamId}` : ""}`;
97
135
  const res = await fetch(url, {
@@ -111,13 +149,11 @@ async function getVercelOidcToken(authToken, projectId, teamId) {
111
149
  }
112
150
  function assertVercelOidcTokenResponse(res) {
113
151
  if (!res || typeof res !== "object") {
114
- throw new TypeError(
115
- "Vercel OIDC token is malformed. Expected an object. Please run `vc env pull` and try again"
116
- );
152
+ throw new TypeError("Vercel OIDC token is malformed. Expected an object.");
117
153
  }
118
154
  if (!("token" in res) || typeof res.token !== "string") {
119
155
  throw new TypeError(
120
- "Vercel OIDC token is malformed. Expected a string-valued token property. Please run `vc env pull` and try again"
156
+ "Vercel OIDC token is malformed. Expected a string-valued token property."
121
157
  );
122
158
  }
123
159
  }
@@ -174,9 +210,7 @@ function loadToken(projectId) {
174
210
  function getTokenPayload(token) {
175
211
  const tokenParts = token.split(".");
176
212
  if (tokenParts.length !== 3) {
177
- throw new import_token_error.VercelOidcTokenError(
178
- "Invalid token. Please run `vc env pull` and try again"
179
- );
213
+ throw new import_token_error.VercelOidcTokenError("Invalid token.");
180
214
  }
181
215
  const base64 = tokenParts[1].replace(/-/g, "+").replace(/_/g, "/");
182
216
  const padded = base64.padEnd(
@@ -193,8 +227,8 @@ function isExpired(token, bufferMs = 0) {
193
227
  assertVercelOidcTokenResponse,
194
228
  findProjectInfo,
195
229
  getTokenPayload,
196
- getVercelDataDir,
197
230
  getVercelOidcToken,
231
+ getVercelOidcTokenFromCli,
198
232
  getVercelToken,
199
233
  isExpired,
200
234
  loadToken,
@@ -23,9 +23,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  var import_vitest = require("vitest");
25
25
  var import_token_util = require("./token-util");
26
- var authConfig = __toESM(require("./auth-config"));
26
+ var cliConfig = __toESM(require("@vercel/cli-config"));
27
27
  var oauth = __toESM(require("./oauth"));
28
28
  var import_auth_errors = require("./auth-errors");
29
+ import_vitest.vi.mock("@vercel/cli-exec", () => ({
30
+ execVercelCli: import_vitest.vi.fn(),
31
+ VercelCliError: class VercelCliError extends Error {
32
+ }
33
+ }));
34
+ import_vitest.vi.mock("@vercel/cli-config", () => ({
35
+ getGlobalPathConfig: import_vitest.vi.fn(() => "/mock/user/data/com.vercel.cli"),
36
+ tryReadAuthConfig: import_vitest.vi.fn(),
37
+ writeAuthConfig: import_vitest.vi.fn()
38
+ }));
29
39
  import_vitest.vi.mock("fs");
30
40
  import_vitest.vi.mock("./token-io", () => ({
31
41
  getUserDataDir: import_vitest.vi.fn(() => "/mock/user/data"),
@@ -45,15 +55,13 @@ import_vitest.vi.mock("./token-io", () => ({
45
55
  expiresAt: Math.floor(Date.now() / 1e3) + 3600
46
56
  // expires in 1 hour
47
57
  };
48
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(validToken);
49
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
50
- });
58
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(validToken);
51
59
  const token = await (0, import_token_util.getVercelToken)();
52
60
  (0, import_vitest.expect)(token).toBe("valid-access-token");
53
- (0, import_vitest.expect)(authConfig.writeAuthConfig).not.toHaveBeenCalled();
61
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).not.toHaveBeenCalled();
54
62
  });
55
63
  (0, import_vitest.it)("should throw AccessTokenMissingError if auth config does not exist", async () => {
56
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(null);
64
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(null);
57
65
  await (0, import_vitest.expect)((0, import_token_util.getVercelToken)()).rejects.toThrow(import_auth_errors.AccessTokenMissingError);
58
66
  });
59
67
  (0, import_vitest.it)("should refresh token if expired and refresh token exists", async () => {
@@ -72,16 +80,15 @@ import_vitest.vi.mock("./token-io", () => ({
72
80
  refresh_token: "new-refresh-token"
73
81
  })
74
82
  };
75
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
76
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
77
- });
83
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(expiredToken);
78
84
  import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
79
85
  const token = await (0, import_token_util.getVercelToken)();
80
86
  (0, import_vitest.expect)(token).toBe("new-access-token");
81
87
  (0, import_vitest.expect)(oauth.refreshTokenRequest).toHaveBeenCalledWith({
82
88
  refresh_token: "valid-refresh-token"
83
89
  });
84
- (0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith(
90
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).toHaveBeenCalledWith(
91
+ import_vitest.expect.any(String),
85
92
  import_vitest.expect.objectContaining({
86
93
  token: "new-access-token",
87
94
  refreshToken: "new-refresh-token",
@@ -89,20 +96,41 @@ import_vitest.vi.mock("./token-io", () => ({
89
96
  })
90
97
  );
91
98
  });
99
+ (0, import_vitest.it)("should refresh token when only refreshToken is present", async () => {
100
+ const refreshOnlyToken = {
101
+ refreshToken: "valid-refresh-token"
102
+ };
103
+ const mockResponse = {
104
+ ok: true,
105
+ json: async () => ({
106
+ access_token: "new-access-token",
107
+ token_type: "Bearer",
108
+ expires_in: 3600
109
+ })
110
+ };
111
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(refreshOnlyToken);
112
+ import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
113
+ const token = await (0, import_token_util.getVercelToken)();
114
+ (0, import_vitest.expect)(token).toBe("new-access-token");
115
+ (0, import_vitest.expect)(oauth.refreshTokenRequest).toHaveBeenCalledWith({
116
+ refresh_token: "valid-refresh-token"
117
+ });
118
+ });
92
119
  (0, import_vitest.it)("should clear auth and throw RefreshAccessTokenFailedError if token expired and no refresh token", async () => {
93
120
  const expiredTokenNoRefresh = {
94
121
  token: "expired-access-token",
95
122
  expiresAt: Math.floor(Date.now() / 1e3) - 3600
96
123
  };
97
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(
124
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(
98
125
  expiredTokenNoRefresh
99
126
  );
100
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
101
- });
102
127
  await (0, import_vitest.expect)((0, import_token_util.getVercelToken)()).rejects.toThrow(
103
128
  import_auth_errors.RefreshAccessTokenFailedError
104
129
  );
105
- (0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith({});
130
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).toHaveBeenCalledWith(
131
+ import_vitest.expect.any(String),
132
+ {}
133
+ );
106
134
  });
107
135
  (0, import_vitest.it)("should clear auth and throw RefreshAccessTokenFailedError if refresh fails with OAuth error", async () => {
108
136
  const expiredToken = {
@@ -117,14 +145,15 @@ import_vitest.vi.mock("./token-io", () => ({
117
145
  error_description: "Refresh token expired"
118
146
  })
119
147
  };
120
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
121
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
122
- });
148
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(expiredToken);
123
149
  import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockErrorResponse);
124
150
  await (0, import_vitest.expect)((0, import_token_util.getVercelToken)()).rejects.toThrow(
125
151
  import_auth_errors.RefreshAccessTokenFailedError
126
152
  );
127
- (0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith({});
153
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).toHaveBeenCalledWith(
154
+ import_vitest.expect.any(String),
155
+ {}
156
+ );
128
157
  });
129
158
  (0, import_vitest.it)("should clear auth and throw RefreshAccessTokenFailedError if refresh fails with network error", async () => {
130
159
  const expiredToken = {
@@ -132,27 +161,26 @@ import_vitest.vi.mock("./token-io", () => ({
132
161
  refreshToken: "valid-refresh-token",
133
162
  expiresAt: Math.floor(Date.now() / 1e3) - 3600
134
163
  };
135
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
136
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
137
- });
164
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(expiredToken);
138
165
  import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockRejectedValue(
139
166
  new Error("Network error")
140
167
  );
141
168
  await (0, import_vitest.expect)((0, import_token_util.getVercelToken)()).rejects.toThrow(
142
169
  import_auth_errors.RefreshAccessTokenFailedError
143
170
  );
144
- (0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith({});
171
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).toHaveBeenCalledWith(
172
+ import_vitest.expect.any(String),
173
+ {}
174
+ );
145
175
  });
146
176
  (0, import_vitest.it)("should treat token as valid if expiresAt is missing (--token case)", async () => {
147
177
  const tokenWithoutExpiry = {
148
178
  token: "cli-provided-token"
149
179
  };
150
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(tokenWithoutExpiry);
151
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
152
- });
180
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(tokenWithoutExpiry);
153
181
  const token = await (0, import_token_util.getVercelToken)();
154
182
  (0, import_vitest.expect)(token).toBe("cli-provided-token");
155
- (0, import_vitest.expect)(authConfig.writeAuthConfig).not.toHaveBeenCalled();
183
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).not.toHaveBeenCalled();
156
184
  });
157
185
  (0, import_vitest.it)("should preserve new refresh token if provided in response", async () => {
158
186
  const expiredToken = {
@@ -169,18 +197,17 @@ import_vitest.vi.mock("./token-io", () => ({
169
197
  refresh_token: "new-refresh-token"
170
198
  })
171
199
  };
172
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
173
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
174
- });
200
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(expiredToken);
175
201
  import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
176
202
  await (0, import_token_util.getVercelToken)();
177
- (0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith(
203
+ (0, import_vitest.expect)(cliConfig.writeAuthConfig).toHaveBeenCalledWith(
204
+ import_vitest.expect.any(String),
178
205
  import_vitest.expect.objectContaining({
179
206
  refreshToken: "new-refresh-token"
180
207
  })
181
208
  );
182
209
  });
183
- (0, import_vitest.it)("should not overwrite refresh token if not provided in response", async () => {
210
+ (0, import_vitest.it)("should preserve refresh token if not provided in response", async () => {
184
211
  const expiredToken = {
185
212
  token: "expired-access-token",
186
213
  refreshToken: "existing-refresh-token",
@@ -195,13 +222,11 @@ import_vitest.vi.mock("./token-io", () => ({
195
222
  // No refresh_token in response
196
223
  })
197
224
  };
198
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
199
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
200
- });
225
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(expiredToken);
201
226
  import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
202
227
  await (0, import_token_util.getVercelToken)();
203
- const writeCall = import_vitest.vi.mocked(authConfig.writeAuthConfig).mock.calls[0][0];
204
- (0, import_vitest.expect)(writeCall).not.toHaveProperty("refreshToken");
228
+ const writeCall = import_vitest.vi.mocked(cliConfig.writeAuthConfig).mock.calls[0][1];
229
+ (0, import_vitest.expect)(writeCall).toHaveProperty("refreshToken", "existing-refresh-token");
205
230
  });
206
231
  (0, import_vitest.it)("should calculate expiresAt correctly from expires_in", async () => {
207
232
  const expiredToken = {
@@ -218,14 +243,12 @@ import_vitest.vi.mock("./token-io", () => ({
218
243
  // 2 hours
219
244
  })
220
245
  };
221
- import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
222
- import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
223
- });
246
+ import_vitest.vi.mocked(cliConfig.tryReadAuthConfig).mockReturnValue(expiredToken);
224
247
  import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
225
248
  const beforeCall = Math.floor(Date.now() / 1e3);
226
249
  await (0, import_token_util.getVercelToken)();
227
250
  const afterCall = Math.floor(Date.now() / 1e3);
228
- const writeCall = import_vitest.vi.mocked(authConfig.writeAuthConfig).mock.calls[0][0];
251
+ const writeCall = import_vitest.vi.mocked(cliConfig.writeAuthConfig).mock.calls[0][1];
229
252
  (0, import_vitest.expect)(writeCall.expiresAt).toBeGreaterThanOrEqual(beforeCall + 7200);
230
253
  (0, import_vitest.expect)(writeCall.expiresAt).toBeLessThanOrEqual(afterCall + 7200);
231
254
  });
package/dist/token.js CHANGED
@@ -21,6 +21,7 @@ __export(token_exports, {
21
21
  refreshToken: () => refreshToken
22
22
  });
23
23
  module.exports = __toCommonJS(token_exports);
24
+ var import_cli_config = require("@vercel/cli-config");
24
25
  var import_token_error = require("./token-error");
25
26
  var import_token_util = require("./token-util");
26
27
  async function refreshToken(options) {
@@ -42,10 +43,15 @@ async function refreshToken(options) {
42
43
  }
43
44
  let maybeToken = (0, import_token_util.loadToken)(projectId);
44
45
  if (!maybeToken || (0, import_token_util.isExpired)((0, import_token_util.getTokenPayload)(maybeToken.token), options?.expirationBufferMs)) {
45
- const authToken = await (0, import_token_util.getVercelToken)({
46
- expirationBufferMs: options?.expirationBufferMs
47
- });
48
- maybeToken = await (0, import_token_util.getVercelOidcToken)(authToken, projectId, teamId);
46
+ const configDir = (0, import_cli_config.getGlobalPathConfig)();
47
+ if ((0, import_cli_config.getLikelyEffectiveCredStorage)(configDir) === "keyring") {
48
+ maybeToken = await (0, import_token_util.getVercelOidcTokenFromCli)(projectId, teamId);
49
+ } else {
50
+ const authToken = await (0, import_token_util.getVercelToken)({
51
+ expirationBufferMs: options?.expirationBufferMs
52
+ });
53
+ maybeToken = await (0, import_token_util.getVercelOidcToken)(authToken, projectId, teamId);
54
+ }
49
55
  if (!maybeToken) {
50
56
  throw new import_token_error.VercelOidcTokenError("Failed to refresh OIDC token");
51
57
  }
@@ -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";
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **getVercelOidcToken**(`options?`): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`string`\>
8
8
 
9
- Defined in: [packages/oidc/src/get-vercel-oidc-token.ts:64](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token.ts#L64)
9
+ Defined in: [packages/oidc/src/get-vercel-oidc-token-with-refresh.ts:64](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token-with-refresh.ts#L64)
10
10
 
11
11
  Gets the current OIDC token from the request context or the environment variable.
12
12
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **getVercelOidcTokenSync**(): `string`
8
8
 
9
- Defined in: [packages/oidc/src/get-vercel-oidc-token.ts:124](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token.ts#L124)
9
+ Defined in: [packages/oidc/src/get-vercel-oidc-token-sync.ts:24](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token-sync.ts#L24)
10
10
 
11
11
  Gets the current OIDC token from the request context or the environment variable.
12
12
 
@@ -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
 
package/package.json CHANGED
@@ -10,6 +10,7 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "exports": {
12
12
  ".": {
13
+ "edge-light": "./dist/index-edge-light.js",
13
14
  "browser": "./dist/index-browser.js",
14
15
  "react-native": "./dist/index-browser.js",
15
16
  "workflow": "./dist/index-browser.js",
@@ -17,7 +18,7 @@
17
18
  "require": "./dist/index.js"
18
19
  }
19
20
  },
20
- "version": "3.4.1",
21
+ "version": "3.6.0",
21
22
  "repository": {
22
23
  "directory": "packages/oidc",
23
24
  "type": "git",
@@ -26,6 +27,10 @@
26
27
  "bugs": {
27
28
  "url": "https://github.com/vercel/vercel/issues"
28
29
  },
30
+ "dependencies": {
31
+ "@vercel/cli-config": "0.2.0",
32
+ "@vercel/cli-exec": "0.1.1"
33
+ },
29
34
  "devDependencies": {
30
35
  "prettier": "3.8.3",
31
36
  "tinyspawn": "1.3.1",
@@ -1,30 +0,0 @@
1
- /**
2
- * Auth configuration stored in ~/.../com.vercel.cli/auth.json
3
- */
4
- export interface AuthConfig {
5
- /** An `access_token` obtained using the OAuth Device Authorization flow. */
6
- token?: string;
7
- /** A `refresh_token` obtained using the OAuth Device Authorization flow. */
8
- refreshToken?: string;
9
- /**
10
- * The absolute time (seconds) when the token expires.
11
- * Used to optimistically check if the token is still valid.
12
- */
13
- expiresAt?: number;
14
- /** Whether to skip writing this config to disk. */
15
- skipWrite?: boolean;
16
- }
17
- /**
18
- * Read the auth config from disk
19
- * Returns null if the file doesn't exist or cannot be read
20
- */
21
- export declare function readAuthConfig(): AuthConfig | null;
22
- /**
23
- * Write the auth config to disk with proper permissions
24
- */
25
- export declare function writeAuthConfig(config: AuthConfig): void;
26
- /**
27
- * Check if an access token is valid (not expired)
28
- * Copied from packages/cli/src/util/client.ts:72-81
29
- */
30
- export declare function isValidAccessToken(authConfig: AuthConfig, expirationBufferMs?: number): boolean;
@@ -1,85 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var auth_config_exports = {};
30
- __export(auth_config_exports, {
31
- isValidAccessToken: () => isValidAccessToken,
32
- readAuthConfig: () => readAuthConfig,
33
- writeAuthConfig: () => writeAuthConfig
34
- });
35
- module.exports = __toCommonJS(auth_config_exports);
36
- var fs = __toESM(require("fs"));
37
- var path = __toESM(require("path"));
38
- var import_token_util = require("./token-util");
39
- function getAuthConfigPath() {
40
- const dataDir = (0, import_token_util.getVercelDataDir)();
41
- if (!dataDir) {
42
- throw new Error(
43
- `Unable to find Vercel CLI data directory. Your platform: ${process.platform}. Supported: darwin, linux, win32.`
44
- );
45
- }
46
- return path.join(dataDir, "auth.json");
47
- }
48
- function readAuthConfig() {
49
- try {
50
- const authPath = getAuthConfigPath();
51
- if (!fs.existsSync(authPath)) {
52
- return null;
53
- }
54
- const content = fs.readFileSync(authPath, "utf8");
55
- if (!content) {
56
- return null;
57
- }
58
- return JSON.parse(content);
59
- } catch (_error) {
60
- return null;
61
- }
62
- }
63
- function writeAuthConfig(config) {
64
- const authPath = getAuthConfigPath();
65
- const authDir = path.dirname(authPath);
66
- if (!fs.existsSync(authDir)) {
67
- fs.mkdirSync(authDir, { mode: 504, recursive: true });
68
- }
69
- fs.writeFileSync(authPath, JSON.stringify(config, null, 2), { mode: 384 });
70
- }
71
- function isValidAccessToken(authConfig, expirationBufferMs = 0) {
72
- if (!authConfig.token)
73
- return false;
74
- if (typeof authConfig.expiresAt !== "number")
75
- return true;
76
- const nowInSeconds = Math.floor(Date.now() / 1e3);
77
- const bufferInSeconds = expirationBufferMs / 1e3;
78
- return authConfig.expiresAt >= nowInSeconds + bufferInSeconds;
79
- }
80
- // Annotate the CommonJS export names for ESM import in node:
81
- 0 && (module.exports = {
82
- isValidAccessToken,
83
- readAuthConfig,
84
- writeAuthConfig
85
- });