@vercel/oidc 3.8.10 → 4.0.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 +6 -0
- package/dist/get-vercel-oidc-token-with-refresh.d.ts +6 -4
- package/dist/get-vercel-oidc-token-with-refresh.js +17 -1
- package/dist/get-vercel-oidc-token-with-refresh.test.js +126 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/functions/getVercelOidcToken.md +7 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -48,18 +48,20 @@ export type GetVercelOidcTokenOptions = (GetVercelOidcTokenBaseOptions & {
|
|
|
48
48
|
skipCache?: never;
|
|
49
49
|
});
|
|
50
50
|
/**
|
|
51
|
-
* Gets the current OIDC token from the request context
|
|
51
|
+
* Gets the current OIDC token from the request context, the Vercel OIDC token
|
|
52
|
+
* file, or the environment variable.
|
|
52
53
|
*
|
|
53
54
|
* Do not cache this value, as it is subject to change in production!
|
|
54
55
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
56
|
+
* It checks for the `x-vercel-oidc-token` header in the request context, then
|
|
57
|
+
* reads the file named by `VERCEL_OIDC_TOKEN_FILE` when set, then falls back to
|
|
58
|
+
* the `VERCEL_OIDC_TOKEN` environment variable.
|
|
57
59
|
*
|
|
58
60
|
* Unlike the `getVercelOidcTokenSync` function, this function will refresh the token if it is expired in a development environment.
|
|
59
61
|
*
|
|
60
62
|
* @param {GetVercelOidcTokenOptions} [options] - Optional configuration for token retrieval.
|
|
61
63
|
* @returns {Promise<string>} A promise that resolves to the OIDC token.
|
|
62
|
-
* @throws {Error} If
|
|
64
|
+
* @throws {Error} If no token source is available or the configured file cannot be read. If the token
|
|
63
65
|
* is expired in a development environment, will also throw an error if the token cannot be refreshed: no CLI credentials are available, CLI credentials are expired, no project configuration is available
|
|
64
66
|
* or the token refresh request fails.
|
|
65
67
|
*
|
|
@@ -31,14 +31,17 @@ __export(get_vercel_oidc_token_with_refresh_exports, {
|
|
|
31
31
|
getVercelOidcToken: () => getVercelOidcToken
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(get_vercel_oidc_token_with_refresh_exports);
|
|
34
|
+
var import_promises = require("node:fs/promises");
|
|
34
35
|
var import_exchange_vercel_oidc_token = require("./exchange-vercel-oidc-token");
|
|
36
|
+
var import_get_context = require("./get-context");
|
|
35
37
|
var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync");
|
|
36
38
|
var import_token_error = require("./token-error");
|
|
37
39
|
async function getVercelOidcToken(options) {
|
|
38
40
|
let token = "";
|
|
39
41
|
let err;
|
|
42
|
+
const tokenFromFile = (0, import_get_context.getContext)().headers?.["x-vercel-oidc-token"] ? null : await tryOidcTokenFromFile();
|
|
40
43
|
try {
|
|
41
|
-
token = (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)();
|
|
44
|
+
token = tokenFromFile ?? (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)();
|
|
42
45
|
} catch (error) {
|
|
43
46
|
err = error;
|
|
44
47
|
}
|
|
@@ -72,6 +75,19 @@ ${error.message}`;
|
|
|
72
75
|
}
|
|
73
76
|
return token;
|
|
74
77
|
}
|
|
78
|
+
async function tryOidcTokenFromFile() {
|
|
79
|
+
const tokenFile = process.env.VERCEL_OIDC_TOKEN_FILE;
|
|
80
|
+
if (!tokenFile) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const token = (await (0, import_promises.readFile)(tokenFile, "utf8")).trim();
|
|
84
|
+
if (!token) {
|
|
85
|
+
throw new import_token_error.VercelOidcTokenError(
|
|
86
|
+
`The Vercel OIDC token file is empty: ${tokenFile}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return token;
|
|
90
|
+
}
|
|
75
91
|
// Annotate the CommonJS export names for ESM import in node:
|
|
76
92
|
0 && (module.exports = {
|
|
77
93
|
getVercelOidcToken
|
|
@@ -30,6 +30,9 @@ var import_token_io = require("./token-io");
|
|
|
30
30
|
var import_cli_config = require("@vercel/cli-config");
|
|
31
31
|
var import_get_vercel_oidc_token_with_refresh = require("./get-vercel-oidc-token-with-refresh");
|
|
32
32
|
var tokenUtil = __toESM(require("./token-util"));
|
|
33
|
+
const requestHeaders = import_vitest.vi.hoisted(() => ({
|
|
34
|
+
current: {}
|
|
35
|
+
}));
|
|
33
36
|
import_vitest.vi.mock("@vercel/cli-config", async (importOriginal) => ({
|
|
34
37
|
...await importOriginal(),
|
|
35
38
|
getGlobalPathConfig: import_vitest.vi.fn(),
|
|
@@ -42,7 +45,7 @@ import_vitest.vi.mock("@vercel/cli-exec", () => ({
|
|
|
42
45
|
}));
|
|
43
46
|
import_vitest.vi.mock("./token-io");
|
|
44
47
|
import_vitest.vi.mock("./get-context", () => ({
|
|
45
|
-
getContext: () => ({ headers:
|
|
48
|
+
getContext: () => ({ headers: requestHeaders.current })
|
|
46
49
|
}));
|
|
47
50
|
(0, import_vitest.describe)("getVercelOidcToken - Error Scenarios", () => {
|
|
48
51
|
let rootDir;
|
|
@@ -54,7 +57,9 @@ import_vitest.vi.mock("./get-context", () => ({
|
|
|
54
57
|
(0, import_vitest.beforeEach)(() => {
|
|
55
58
|
import_vitest.vi.clearAllMocks();
|
|
56
59
|
import_vitest.vi.restoreAllMocks();
|
|
60
|
+
requestHeaders.current = {};
|
|
57
61
|
process.env.VERCEL_OIDC_TOKEN = void 0;
|
|
62
|
+
delete process.env.VERCEL_OIDC_TOKEN_FILE;
|
|
58
63
|
const random = `test-${(0, import_crypto.randomUUID)()}`;
|
|
59
64
|
rootDir = path.join(os.tmpdir(), random);
|
|
60
65
|
userDataDir = path.join(rootDir, "data");
|
|
@@ -198,6 +203,124 @@ import_vitest.vi.mock("./get-context", () => ({
|
|
|
198
203
|
const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)();
|
|
199
204
|
(0, import_vitest.expect)(token).toBe(validToken);
|
|
200
205
|
});
|
|
206
|
+
(0, import_vitest.test)("reads the Vercel OIDC token file on each call after rotation", async () => {
|
|
207
|
+
delete process.env.VERCEL_OIDC_TOKEN;
|
|
208
|
+
const tokenFile = path.join(rootDir, "oidc-token");
|
|
209
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = tokenFile;
|
|
210
|
+
const firstToken = createValidToken("first");
|
|
211
|
+
const secondToken = createValidToken("second");
|
|
212
|
+
fs.writeFileSync(tokenFile, `${firstToken}
|
|
213
|
+
`);
|
|
214
|
+
(0, import_vitest.expect)(await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).toBe(firstToken);
|
|
215
|
+
fs.writeFileSync(tokenFile, `${secondToken}
|
|
216
|
+
`);
|
|
217
|
+
(0, import_vitest.expect)(await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).toBe(secondToken);
|
|
218
|
+
});
|
|
219
|
+
(0, import_vitest.test)("prefers the advertised file over a static environment token", async () => {
|
|
220
|
+
process.env.VERCEL_OIDC_TOKEN = createValidToken("stale");
|
|
221
|
+
const tokenFile = path.join(rootDir, "oidc-token");
|
|
222
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = tokenFile;
|
|
223
|
+
const currentToken = createValidToken("current");
|
|
224
|
+
fs.writeFileSync(tokenFile, currentToken);
|
|
225
|
+
const fetchSpy = import_vitest.vi.spyOn(globalThis, "fetch");
|
|
226
|
+
(0, import_vitest.expect)(
|
|
227
|
+
await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
|
|
228
|
+
team: teamId,
|
|
229
|
+
project: projectId,
|
|
230
|
+
expirationBufferMs: 6e4
|
|
231
|
+
})
|
|
232
|
+
).toBe(currentToken);
|
|
233
|
+
(0, import_vitest.expect)(fetchSpy).not.toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
(0, import_vitest.test)("keeps the request token ahead of the advertised file", async () => {
|
|
236
|
+
const requestToken = createValidToken("request");
|
|
237
|
+
requestHeaders.current = { "x-vercel-oidc-token": requestToken };
|
|
238
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = path.join(rootDir, "missing-token");
|
|
239
|
+
const fetchSpy = import_vitest.vi.spyOn(globalThis, "fetch");
|
|
240
|
+
(0, import_vitest.expect)(
|
|
241
|
+
await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
|
|
242
|
+
team: teamId,
|
|
243
|
+
project: projectId,
|
|
244
|
+
expirationBufferMs: 6e4
|
|
245
|
+
})
|
|
246
|
+
).toBe(requestToken);
|
|
247
|
+
(0, import_vitest.expect)(fetchSpy).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
(0, import_vitest.test)("includes the file path when the Vercel OIDC token file is empty", async () => {
|
|
250
|
+
delete process.env.VERCEL_OIDC_TOKEN;
|
|
251
|
+
const tokenFile = path.join(rootDir, "oidc-token");
|
|
252
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = tokenFile;
|
|
253
|
+
fs.writeFileSync(tokenFile, " \n");
|
|
254
|
+
await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(
|
|
255
|
+
`The Vercel OIDC token file is empty: ${tokenFile}`
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
(0, import_vitest.test)("rejects a missing advertised token file instead of using the environment token", async () => {
|
|
259
|
+
process.env.VERCEL_OIDC_TOKEN = createValidToken("environment");
|
|
260
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = path.join(rootDir, "missing-token");
|
|
261
|
+
await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow("ENOENT");
|
|
262
|
+
});
|
|
263
|
+
(0, import_vitest.test)("refreshes an expired file token using the provided team and project", async () => {
|
|
264
|
+
delete process.env.VERCEL_OIDC_TOKEN;
|
|
265
|
+
const tokenFile = path.join(rootDir, "oidc-token");
|
|
266
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = tokenFile;
|
|
267
|
+
fs.writeFileSync(tokenFile, createExpiredToken());
|
|
268
|
+
fs.writeFileSync(
|
|
269
|
+
path.join(cliDataDir, "auth.json"),
|
|
270
|
+
JSON.stringify({ token: "test-auth-token" })
|
|
271
|
+
);
|
|
272
|
+
const refreshedToken = createValidToken("refreshed");
|
|
273
|
+
const fetchSpy = import_vitest.vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
274
|
+
new Response(JSON.stringify({ token: refreshedToken }))
|
|
275
|
+
);
|
|
276
|
+
(0, import_vitest.expect)(await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({ team: teamId, project: projectId })).toBe(
|
|
277
|
+
refreshedToken
|
|
278
|
+
);
|
|
279
|
+
(0, import_vitest.expect)(fetchSpy).toHaveBeenCalledWith(
|
|
280
|
+
`https://api.vercel.com/v1/projects/${projectId}/token?source=vercel-oidc-refresh&teamId=${teamId}`,
|
|
281
|
+
import_vitest.expect.objectContaining({ method: "POST" })
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
(0, import_vitest.test)("refreshes a file token within the expiration buffer before audience exchange", async () => {
|
|
285
|
+
delete process.env.VERCEL_OIDC_TOKEN;
|
|
286
|
+
const tokenFile = path.join(rootDir, "oidc-token");
|
|
287
|
+
process.env.VERCEL_OIDC_TOKEN_FILE = tokenFile;
|
|
288
|
+
fs.writeFileSync(
|
|
289
|
+
tokenFile,
|
|
290
|
+
createValidToken("near-expiry", Math.floor(Date.now() / 1e3) + 30)
|
|
291
|
+
);
|
|
292
|
+
fs.writeFileSync(
|
|
293
|
+
path.join(cliDataDir, "auth.json"),
|
|
294
|
+
JSON.stringify({ token: "test-auth-token" })
|
|
295
|
+
);
|
|
296
|
+
const refreshedToken = createValidToken("refreshed");
|
|
297
|
+
const fetchSpy = import_vitest.vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
|
|
298
|
+
new Response(JSON.stringify({ token: refreshedToken }))
|
|
299
|
+
).mockResolvedValueOnce(
|
|
300
|
+
new Response(JSON.stringify({ token: "audience-specific-token" }))
|
|
301
|
+
);
|
|
302
|
+
(0, import_vitest.expect)(
|
|
303
|
+
await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({
|
|
304
|
+
team: teamId,
|
|
305
|
+
project: projectId,
|
|
306
|
+
expirationBufferMs: 6e4,
|
|
307
|
+
audience: "custom-audience",
|
|
308
|
+
jti: "custom-jti",
|
|
309
|
+
skipCache: true
|
|
310
|
+
})
|
|
311
|
+
).toBe("audience-specific-token");
|
|
312
|
+
(0, import_vitest.expect)(fetchSpy).toHaveBeenCalledWith(
|
|
313
|
+
"https://oidc.vercel.com/~token",
|
|
314
|
+
import_vitest.expect.objectContaining({
|
|
315
|
+
method: "POST",
|
|
316
|
+
body: JSON.stringify({
|
|
317
|
+
token: refreshedToken,
|
|
318
|
+
aud: "custom-audience",
|
|
319
|
+
jti: "custom-jti"
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
});
|
|
201
324
|
(0, import_vitest.test)("should refresh when token is expired but all configs are valid", async () => {
|
|
202
325
|
fs.writeFileSync(
|
|
203
326
|
path.join(cliDataDir, "auth.json"),
|
|
@@ -356,14 +479,14 @@ function createExpiredToken() {
|
|
|
356
479
|
).toString("base64url");
|
|
357
480
|
return `${header}.${payload}.fake_signature`;
|
|
358
481
|
}
|
|
359
|
-
function createValidToken(value = "valid-token") {
|
|
482
|
+
function createValidToken(value = "valid-token", expiresAt = Math.floor(Date.now() / 1e3) + 43200) {
|
|
360
483
|
const header = Buffer.from(
|
|
361
484
|
JSON.stringify({ alg: "RS256", typ: "JWT" })
|
|
362
485
|
).toString("base64url");
|
|
363
486
|
const payload = Buffer.from(
|
|
364
487
|
JSON.stringify({
|
|
365
488
|
sub: "test-sub",
|
|
366
|
-
exp:
|
|
489
|
+
exp: expiresAt,
|
|
367
490
|
iat: Math.floor(Date.now() / 1e3)
|
|
368
491
|
})
|
|
369
492
|
).toString("base64url");
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "
|
|
1
|
+
export declare const version = "4.0.0";
|
package/dist/version.js
CHANGED
|
@@ -21,7 +21,7 @@ __export(version_exports, {
|
|
|
21
21
|
version: () => version
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(version_exports);
|
|
24
|
-
const version = "
|
|
24
|
+
const version = "4.0.0";
|
|
25
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26
26
|
0 && (module.exports = {
|
|
27
27
|
version
|
|
@@ -6,14 +6,16 @@
|
|
|
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-with-refresh.ts:
|
|
9
|
+
Defined in: [packages/oidc/src/get-vercel-oidc-token-with-refresh.ts:100](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token-with-refresh.ts#L100)
|
|
10
10
|
|
|
11
|
-
Gets the current OIDC token from the request context
|
|
11
|
+
Gets the current OIDC token from the request context, the Vercel OIDC token
|
|
12
|
+
file, or the environment variable.
|
|
12
13
|
|
|
13
14
|
Do not cache this value, as it is subject to change in production!
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
It checks for the `x-vercel-oidc-token` header in the request context, then
|
|
17
|
+
reads the file named by `VERCEL_OIDC_TOKEN_FILE` when set, then falls back to
|
|
18
|
+
the `VERCEL_OIDC_TOKEN` environment variable.
|
|
17
19
|
|
|
18
20
|
Unlike the `getVercelOidcTokenSync` function, this function will refresh the token if it is expired in a development environment.
|
|
19
21
|
|
|
@@ -33,7 +35,7 @@ A promise that resolves to the OIDC token.
|
|
|
33
35
|
|
|
34
36
|
## Throws
|
|
35
37
|
|
|
36
|
-
If
|
|
38
|
+
If no token source is available or the configured file cannot be read. If the token
|
|
37
39
|
is expired in a development environment, will also throw an error if the token cannot be refreshed: no CLI credentials are available, CLI credentials are expired, no project configuration is available
|
|
38
40
|
or the token refresh request fails.
|
|
39
41
|
|