@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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @vercel/oidc
2
2
 
3
+ ## 3.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 01cf6c2: Add `verifyVercelOidcToken` for verifying Vercel OIDC tokens against Vercel's remote JWKS.
8
+
9
+ ## 3.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 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).
14
+
15
+ `@vercel/oidc` supports keyring-stored authentication credentials by delegating the OIDC minting to the CLI executable via `@vercel/cli-exec`.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [fddeb55]
20
+ - @vercel/cli-config@0.2.0
21
+
3
22
  ## 3.5.0
4
23
 
5
24
  ### Minor 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:
@@ -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_cli_config = require("@vercel/cli-config");
30
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)) {
@@ -129,28 +141,24 @@ 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
148
  await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
137
- /Vercel OIDC token is malformed\. Expected a string-valued token property\. Please run `vc env pull` and try again/
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_with_refresh.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_with_refresh.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;
@@ -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
  });
@@ -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(
@@ -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
  });
@@ -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
  }