@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 +19 -0
- package/dist/auth-errors.js +4 -1
- package/dist/get-vercel-oidc-token-with-refresh.test.js +17 -9
- package/dist/index-browser.d.ts +1 -0
- package/dist/index-browser.js +5 -2
- package/dist/index-browser.test.js +5 -0
- package/dist/index-edge-light.d.ts +1 -0
- package/dist/index-edge-light.js +5 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/token-util.d.ts +1 -1
- package/dist/token-util.js +63 -29
- package/dist/token-util.test.js +63 -40
- package/dist/token.js +10 -4
- package/dist/token.test.js +91 -1
- package/dist/verify-vercel-oidc-token.d.ts +42 -0
- package/dist/verify-vercel-oidc-token.js +121 -0
- package/dist/verify-vercel-oidc-token.test.d.ts +1 -0
- package/dist/verify-vercel-oidc-token.test.js +318 -0
- package/docs/README.md +5 -0
- package/docs/functions/getVercelToken.md +1 -1
- package/docs/functions/verifyVercelOidcToken.md +57 -0
- package/docs/type-aliases/VercelOidcPayload.md +39 -0
- package/package.json +6 -1
- package/dist/auth-config.d.ts +0 -30
- package/dist/auth-config.js +0 -85
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
|
package/dist/auth-errors.js
CHANGED
|
@@ -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."
|
|
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.
|
|
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
|
|
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.
|
|
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;
|
package/dist/index-browser.d.ts
CHANGED
|
@@ -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;
|
package/dist/index-browser.js
CHANGED
|
@@ -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
|
/**
|
package/dist/index-edge-light.js
CHANGED
|
@@ -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
|
});
|
package/dist/token-util.d.ts
CHANGED
|
@@ -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(): {
|
package/dist/token-util.js
CHANGED
|
@@ -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
|
|
59
|
-
|
|
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 (
|
|
56
|
+
if (isValidAccessToken(authConfig, options?.expirationBufferMs)) {
|
|
63
57
|
return authConfig.token;
|
|
64
58
|
}
|
|
65
59
|
if (!authConfig.refreshToken) {
|
|
66
|
-
(0,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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,
|
package/dist/token-util.test.js
CHANGED
|
@@ -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
|
|
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.
|
|
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)(
|
|
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.
|
|
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.
|
|
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)(
|
|
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.
|
|
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)(
|
|
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.
|
|
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)(
|
|
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.
|
|
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)(
|
|
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.
|
|
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)(
|
|
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.
|
|
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)(
|
|
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
|
|
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.
|
|
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(
|
|
204
|
-
(0, import_vitest.expect)(writeCall).
|
|
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.
|
|
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(
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
}
|