@vercel/oidc
Advanced tools
| /** | ||
| * Gets the current OIDC token from the request context or the environment variable. | ||
| * | ||
| * Do not cache this value, as it is subject to change in production! | ||
| * | ||
| * This function is used to retrieve the OIDC token from the request context or the environment variable. | ||
| * 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. | ||
| * | ||
| * This function will not refresh the token if it is expired. For refreshing the token, use the @{link getVercelOidcToken} function. | ||
| * | ||
| * @returns {string} The OIDC token. | ||
| * @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. | ||
| * | ||
| * @example | ||
| * | ||
| * ```js | ||
| * // Using the OIDC token | ||
| * const token = getVercelOidcTokenSync(); | ||
| * console.log('OIDC Token:', token); | ||
| * ``` | ||
| */ | ||
| export declare function getVercelOidcTokenSync(): string; |
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var get_vercel_oidc_token_sync_exports = {}; | ||
| __export(get_vercel_oidc_token_sync_exports, { | ||
| getVercelOidcTokenSync: () => getVercelOidcTokenSync | ||
| }); | ||
| module.exports = __toCommonJS(get_vercel_oidc_token_sync_exports); | ||
| var import_get_context = require("./get-context"); | ||
| function getVercelOidcTokenSync() { | ||
| const token = (0, import_get_context.getContext)().headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN; | ||
| if (!token) { | ||
| throw new Error( | ||
| `The 'x-vercel-oidc-token' header is missing from the request.` | ||
| ); | ||
| } | ||
| return token; | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| getVercelOidcTokenSync | ||
| }); |
| /** | ||
| * Options for getting the Vercel OIDC token. | ||
| */ | ||
| export interface GetVercelOidcTokenOptions { | ||
| /** | ||
| * Optional team ID (team_*) or slug to use for token refresh. | ||
| * When provided, this team will be used instead of reading from `.vercel/project.json`. | ||
| */ | ||
| team?: string; | ||
| /** | ||
| * Optional project ID (prj_*) or slug to use for token refresh. | ||
| * When provided, this project will be used instead of reading from `.vercel/project.json`. | ||
| */ | ||
| project?: string; | ||
| /** | ||
| * Optional time buffer in milliseconds before token expiry to consider it expired. | ||
| * When provided, the token will be refreshed if it expires within this buffer time. | ||
| * @default 0 | ||
| */ | ||
| expirationBufferMs?: number; | ||
| } | ||
| /** | ||
| * Gets the current OIDC token from the request context or the environment variable. | ||
| * | ||
| * Do not cache this value, as it is subject to change in production! | ||
| * | ||
| * This function is used to retrieve the OIDC token from the request context or the environment variable. | ||
| * 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. | ||
| * | ||
| * Unlike the `getVercelOidcTokenSync` function, this function will refresh the token if it is expired in a development environment. | ||
| * | ||
| * @param {GetVercelOidcTokenOptions} [options] - Optional configuration for token retrieval. | ||
| * @returns {Promise<string>} A promise that resolves to the OIDC token. | ||
| * @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. If the token | ||
| * 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 | ||
| * or the token refresh request fails. | ||
| * | ||
| * @example | ||
| * | ||
| * ```js | ||
| * // Using the OIDC token | ||
| * getVercelOidcToken().then((token) => { | ||
| * console.log('OIDC Token:', token); | ||
| * }).catch((error) => { | ||
| * console.error('Error:', error.message); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @example | ||
| * | ||
| * ```js | ||
| * // Using the OIDC token with explicit team and project (supports IDs and slugs) | ||
| * getVercelOidcToken({ team: 'my-team', project: 'my-project' }).then((token) => { | ||
| * console.log('OIDC Token:', token); | ||
| * }).catch((error) => { | ||
| * console.error('Error:', error.message); | ||
| * }); | ||
| * ``` | ||
| */ | ||
| export declare function getVercelOidcToken(options?: GetVercelOidcTokenOptions): Promise<string>; |
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var get_vercel_oidc_token_with_refresh_exports = {}; | ||
| __export(get_vercel_oidc_token_with_refresh_exports, { | ||
| getVercelOidcToken: () => getVercelOidcToken | ||
| }); | ||
| module.exports = __toCommonJS(get_vercel_oidc_token_with_refresh_exports); | ||
| var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync"); | ||
| var import_token_error = require("./token-error"); | ||
| async function getVercelOidcToken(options) { | ||
| let token = ""; | ||
| let err; | ||
| try { | ||
| token = (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)(); | ||
| } catch (error) { | ||
| err = error; | ||
| } | ||
| try { | ||
| const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([ | ||
| await import("./token-util.js"), | ||
| await import("./token.js") | ||
| ]); | ||
| if (!token || isExpired(getTokenPayload(token), options?.expirationBufferMs)) { | ||
| await refreshToken(options); | ||
| token = (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)(); | ||
| } | ||
| } catch (error) { | ||
| let message = err instanceof Error ? err.message : ""; | ||
| if (error instanceof Error) { | ||
| message = `${message} | ||
| ${error.message}`; | ||
| } | ||
| if (message) { | ||
| throw new import_token_error.VercelOidcTokenError(message); | ||
| } | ||
| throw error; | ||
| } | ||
| return token; | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| getVercelOidcToken | ||
| }); |
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var import_crypto = require("crypto"); | ||
| var import_vitest = require("vitest"); | ||
| var os = __toESM(require("os")); | ||
| var path = __toESM(require("path")); | ||
| var fs = __toESM(require("fs")); | ||
| var import_token_io = require("./token-io"); | ||
| var import_get_vercel_oidc_token_with_refresh = require("./get-vercel-oidc-token-with-refresh"); | ||
| var tokenUtil = __toESM(require("./token-util")); | ||
| import_vitest.vi.mock("./token-io"); | ||
| import_vitest.vi.mock("./get-context", () => ({ | ||
| getContext: () => ({ headers: {} }) | ||
| })); | ||
| (0, import_vitest.describe)("getVercelOidcToken - Error Scenarios", () => { | ||
| let rootDir; | ||
| let userDataDir; | ||
| let cliDataDir; | ||
| let tokenDataDir; | ||
| const projectId = "prj_test123"; | ||
| const teamId = "team_test456"; | ||
| (0, import_vitest.beforeEach)(() => { | ||
| import_vitest.vi.clearAllMocks(); | ||
| import_vitest.vi.restoreAllMocks(); | ||
| process.env.VERCEL_OIDC_TOKEN = void 0; | ||
| const random = `test-${(0, import_crypto.randomUUID)()}`; | ||
| rootDir = path.join(os.tmpdir(), random); | ||
| userDataDir = path.join(rootDir, "data"); | ||
| cliDataDir = path.join(userDataDir, "com.vercel.cli"); | ||
| tokenDataDir = path.join(userDataDir, "com.vercel.token"); | ||
| fs.mkdirSync(cliDataDir, { recursive: true }); | ||
| fs.mkdirSync(tokenDataDir, { recursive: true }); | ||
| fs.mkdirSync(path.join(rootDir, ".vercel"), { | ||
| recursive: true | ||
| }); | ||
| import_vitest.vi.spyOn(process, "cwd").mockReturnValue(rootDir); | ||
| import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(rootDir); | ||
| import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(userDataDir); | ||
| }); | ||
| (0, import_vitest.afterEach)(() => { | ||
| if (fs.existsSync(rootDir)) { | ||
| fs.rmSync(rootDir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
| (0, import_vitest.test)("should throw AccessTokenMissingError when CLI auth file is missing", async () => { | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /No authentication found/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when project.json is missing", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /project\.json not found, have you linked your project with `vc link\?`/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when root directory cannot be found", async () => { | ||
| import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(null); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /Unable to find project root directory\. Have you linked your project with `vc link\?`/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when user data directory cannot be found", async () => { | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(null); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /Unable to find user data directory\. Please reach out to Vercel support\./ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when API returns non-200", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockRejectedValue( | ||
| new Error("Failed to refresh OIDC token: Unauthorized") | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /Failed to refresh OIDC token: Unauthorized/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when token response is malformed", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockRejectedValue( | ||
| new TypeError( | ||
| "Vercel OIDC token is malformed. Expected a string-valued token property. Please run `vc env pull` and try again" | ||
| ) | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /Vercel OIDC token is malformed\. Expected a string-valued token property\. Please run `vc env pull` and try again/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when token has invalid format", async () => { | ||
| process.env.VERCEL_OIDC_TOKEN = "not-a-valid-jwt-token"; | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockImplementation(() => { | ||
| throw new Error("Invalid token. Please run `vc env pull` and try again"); | ||
| }); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /Invalid token\. Please run `vc env pull` and try again/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw error when token expiry check fails", async () => { | ||
| process.env.VERCEL_OIDC_TOKEN = "not-a-jwt-token"; | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow( | ||
| /Invalid token\. Please run `vc env pull` and try again/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should fail when no token exists and no CLI credentials available", async () => { | ||
| process.env.VERCEL_OIDC_TOKEN = void 0; | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(/Invalid token/); | ||
| }); | ||
| (0, import_vitest.test)("should propagate filesystem errors when saving token fails", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: "new-valid-token" | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "saveToken").mockImplementation(() => { | ||
| throw new Error("EACCES: permission denied"); | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)()).rejects.toThrow(/EACCES|permission/i); | ||
| }); | ||
| (0, import_vitest.test)("should succeed when valid token exists in env", async () => { | ||
| const validToken = createValidToken(); | ||
| process.env.VERCEL_OIDC_TOKEN = validToken; | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)(); | ||
| (0, import_vitest.expect)(token).toBe(validToken); | ||
| }); | ||
| (0, import_vitest.test)("should refresh when token is expired but all configs are valid", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| const newToken = createValidToken("new-token"); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)(); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| }); | ||
| (0, import_vitest.test)("should use provided team and project for refresh instead of reading project.json", async () => { | ||
| const customProjectId = "prj_custom123"; | ||
| const customTeamId = "team_custom456"; | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| const newToken = createValidToken("custom-token"); | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({ | ||
| team: customTeamId, | ||
| project: customProjectId | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).toHaveBeenCalledWith( | ||
| "test-auth-token", | ||
| customProjectId, | ||
| customTeamId | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should use provided project and read team from project.json", async () => { | ||
| const customProjectId = "prj_custom234"; | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId: "original-project-id", orgId: teamId }) | ||
| ); | ||
| const newToken = createValidToken("partial-custom-token"); | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({ | ||
| project: customProjectId | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).toHaveBeenCalledWith( | ||
| "test-auth-token", | ||
| customProjectId, | ||
| teamId | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should use provided team and read project from project.json", async () => { | ||
| const customTeamId = "team_custom789"; | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: "original-team-id" }) | ||
| ); | ||
| const newToken = createValidToken("partial-custom-token-2"); | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({ | ||
| team: customTeamId | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).toHaveBeenCalledWith( | ||
| "test-auth-token", | ||
| projectId, | ||
| customTeamId | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should not refresh when token is valid even with options provided", async () => { | ||
| const validToken = createValidToken(); | ||
| process.env.VERCEL_OIDC_TOKEN = validToken; | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: "should-not-be-called" | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| const token = await (0, import_get_vercel_oidc_token_with_refresh.getVercelOidcToken)({ | ||
| team: "custom-team", | ||
| project: "custom-project" | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(validToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
| function createExpiredToken() { | ||
| const header = Buffer.from( | ||
| JSON.stringify({ alg: "RS256", typ: "JWT" }) | ||
| ).toString("base64url"); | ||
| const payload = Buffer.from( | ||
| JSON.stringify({ | ||
| sub: "test-sub", | ||
| exp: 1, | ||
| iat: 1 | ||
| }) | ||
| ).toString("base64url"); | ||
| return `${header}.${payload}.fake_signature`; | ||
| } | ||
| function createValidToken(value = "valid-token") { | ||
| const header = Buffer.from( | ||
| JSON.stringify({ alg: "RS256", typ: "JWT" }) | ||
| ).toString("base64url"); | ||
| const payload = Buffer.from( | ||
| JSON.stringify({ | ||
| sub: "test-sub", | ||
| exp: Math.floor(Date.now() / 1e3) + 43200, | ||
| iat: Math.floor(Date.now() / 1e3) | ||
| }) | ||
| ).toString("base64url"); | ||
| return `${header}.${payload}.fake_signature_${value}`; | ||
| } |
| export { getContext } from './get-context'; | ||
| export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors'; | ||
| export { getVercelOidcTokenSync } from './get-vercel-oidc-token-sync'; | ||
| /** | ||
| * Gets the current OIDC token in Edge Runtime. | ||
| * | ||
| * Edge Runtime does not support automatic token refresh, so this returns the | ||
| * request-scoped token without checking expiration. | ||
| */ | ||
| export declare function getVercelOidcToken(): Promise<string>; | ||
| export declare function getVercelToken(): Promise<string>; |
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var index_edge_light_exports = {}; | ||
| __export(index_edge_light_exports, { | ||
| AccessTokenMissingError: () => import_auth_errors.AccessTokenMissingError, | ||
| RefreshAccessTokenFailedError: () => import_auth_errors.RefreshAccessTokenFailedError, | ||
| getContext: () => import_get_context.getContext, | ||
| getVercelOidcToken: () => getVercelOidcToken, | ||
| getVercelOidcTokenSync: () => import_get_vercel_oidc_token_sync2.getVercelOidcTokenSync, | ||
| getVercelToken: () => getVercelToken | ||
| }); | ||
| module.exports = __toCommonJS(index_edge_light_exports); | ||
| var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync"); | ||
| var import_get_context = require("./get-context"); | ||
| var import_auth_errors = require("./auth-errors"); | ||
| var import_get_vercel_oidc_token_sync2 = require("./get-vercel-oidc-token-sync"); | ||
| async function getVercelOidcToken() { | ||
| return (0, import_get_vercel_oidc_token_sync.getVercelOidcTokenSync)(); | ||
| } | ||
| async function getVercelToken() { | ||
| throw new Error("getVercelToken is not supported in Edge Runtime"); | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| AccessTokenMissingError, | ||
| RefreshAccessTokenFailedError, | ||
| getContext, | ||
| getVercelOidcToken, | ||
| getVercelOidcTokenSync, | ||
| getVercelToken | ||
| }); |
+6
-0
| # @vercel/oidc | ||
| ## 3.5.0 | ||
| ### Minor Changes | ||
| - 5a700dc: Add conditional edge-light export to support Edge Runtime | ||
| ## 3.4.1 | ||
@@ -4,0 +10,0 @@ |
+2
-1
@@ -1,4 +0,5 @@ | ||
| export { getVercelOidcToken, getVercelOidcTokenSync, } from './get-vercel-oidc-token'; | ||
| export { getVercelOidcToken } from './get-vercel-oidc-token-with-refresh'; | ||
| export { getVercelOidcTokenSync } from './get-vercel-oidc-token-sync'; | ||
| export { getContext } from './get-context'; | ||
| export { AccessTokenMissingError, RefreshAccessTokenFailedError, } from './auth-errors'; | ||
| export { getVercelToken } from './token-util'; |
+4
-3
@@ -24,8 +24,9 @@ "use strict"; | ||
| getContext: () => import_get_context.getContext, | ||
| getVercelOidcToken: () => import_get_vercel_oidc_token.getVercelOidcToken, | ||
| getVercelOidcTokenSync: () => import_get_vercel_oidc_token.getVercelOidcTokenSync, | ||
| getVercelOidcToken: () => import_get_vercel_oidc_token_with_refresh.getVercelOidcToken, | ||
| getVercelOidcTokenSync: () => import_get_vercel_oidc_token_sync.getVercelOidcTokenSync, | ||
| getVercelToken: () => import_token_util.getVercelToken | ||
| }); | ||
| module.exports = __toCommonJS(src_exports); | ||
| var import_get_vercel_oidc_token = require("./get-vercel-oidc-token"); | ||
| var import_get_vercel_oidc_token_with_refresh = require("./get-vercel-oidc-token-with-refresh"); | ||
| var import_get_vercel_oidc_token_sync = require("./get-vercel-oidc-token-sync"); | ||
| var import_get_context = require("./get-context"); | ||
@@ -32,0 +33,0 @@ var import_auth_errors = require("./auth-errors"); |
@@ -9,3 +9,3 @@ [**@vercel/oidc**](../README.md) | ||
| 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) | ||
| 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) | ||
@@ -12,0 +12,0 @@ Gets the current OIDC token from the request context or the environment variable. |
@@ -9,3 +9,3 @@ [**@vercel/oidc**](../README.md) | ||
| 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) | ||
| 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) | ||
@@ -12,0 +12,0 @@ Gets the current OIDC token from the request context or the environment variable. |
+2
-1
@@ -13,2 +13,3 @@ { | ||
| ".": { | ||
| "edge-light": "./dist/index-edge-light.js", | ||
| "browser": "./dist/index-browser.js", | ||
@@ -21,3 +22,3 @@ "react-native": "./dist/index-browser.js", | ||
| }, | ||
| "version": "3.4.1", | ||
| "version": "3.5.0", | ||
| "repository": { | ||
@@ -24,0 +25,0 @@ "directory": "packages/oidc", |
| /** | ||
| * Options for getting the Vercel OIDC token. | ||
| */ | ||
| export interface GetVercelOidcTokenOptions { | ||
| /** | ||
| * Optional team ID (team_*) or slug to use for token refresh. | ||
| * When provided, this team will be used instead of reading from `.vercel/project.json`. | ||
| */ | ||
| team?: string; | ||
| /** | ||
| * Optional project ID (prj_*) or slug to use for token refresh. | ||
| * When provided, this project will be used instead of reading from `.vercel/project.json`. | ||
| */ | ||
| project?: string; | ||
| /** | ||
| * Optional time buffer in milliseconds before token expiry to consider it expired. | ||
| * When provided, the token will be refreshed if it expires within this buffer time. | ||
| * @default 0 | ||
| */ | ||
| expirationBufferMs?: number; | ||
| } | ||
| /** | ||
| * Gets the current OIDC token from the request context or the environment variable. | ||
| * | ||
| * Do not cache this value, as it is subject to change in production! | ||
| * | ||
| * This function is used to retrieve the OIDC token from the request context or the environment variable. | ||
| * 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. | ||
| * | ||
| * Unlike the `getVercelOidcTokenSync` function, this function will refresh the token if it is expired in a development environment. | ||
| * | ||
| * @param {GetVercelOidcTokenOptions} [options] - Optional configuration for token retrieval. | ||
| * @returns {Promise<string>} A promise that resolves to the OIDC token. | ||
| * @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. If the token | ||
| * 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 | ||
| * or the token refresh request fails. | ||
| * | ||
| * @example | ||
| * | ||
| * ```js | ||
| * // Using the OIDC token | ||
| * getVercelOidcToken().then((token) => { | ||
| * console.log('OIDC Token:', token); | ||
| * }).catch((error) => { | ||
| * console.error('Error:', error.message); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @example | ||
| * | ||
| * ```js | ||
| * // Using the OIDC token with explicit team and project (supports IDs and slugs) | ||
| * getVercelOidcToken({ team: 'my-team', project: 'my-project' }).then((token) => { | ||
| * console.log('OIDC Token:', token); | ||
| * }).catch((error) => { | ||
| * console.error('Error:', error.message); | ||
| * }); | ||
| * ``` | ||
| */ | ||
| export declare function getVercelOidcToken(options?: GetVercelOidcTokenOptions): Promise<string>; | ||
| /** | ||
| * Gets the current OIDC token from the request context or the environment variable. | ||
| * | ||
| * Do not cache this value, as it is subject to change in production! | ||
| * | ||
| * This function is used to retrieve the OIDC token from the request context or the environment variable. | ||
| * 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. | ||
| * | ||
| * This function will not refresh the token if it is expired. For refreshing the token, use the @{link getVercelOidcToken} function. | ||
| * | ||
| * @returns {string} The OIDC token. | ||
| * @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. | ||
| * | ||
| * @example | ||
| * | ||
| * ```js | ||
| * // Using the OIDC token | ||
| * const token = getVercelOidcTokenSync(); | ||
| * console.log('OIDC Token:', token); | ||
| * ``` | ||
| */ | ||
| export declare function getVercelOidcTokenSync(): string; |
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var get_vercel_oidc_token_exports = {}; | ||
| __export(get_vercel_oidc_token_exports, { | ||
| getVercelOidcToken: () => getVercelOidcToken, | ||
| getVercelOidcTokenSync: () => getVercelOidcTokenSync | ||
| }); | ||
| module.exports = __toCommonJS(get_vercel_oidc_token_exports); | ||
| var import_get_context = require("./get-context"); | ||
| var import_token_error = require("./token-error"); | ||
| async function getVercelOidcToken(options) { | ||
| let token = ""; | ||
| let err; | ||
| try { | ||
| token = getVercelOidcTokenSync(); | ||
| } catch (error) { | ||
| err = error; | ||
| } | ||
| try { | ||
| const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([ | ||
| await import("./token-util.js"), | ||
| await import("./token.js") | ||
| ]); | ||
| if (!token || isExpired(getTokenPayload(token), options?.expirationBufferMs)) { | ||
| await refreshToken(options); | ||
| token = getVercelOidcTokenSync(); | ||
| } | ||
| } catch (error) { | ||
| let message = err instanceof Error ? err.message : ""; | ||
| if (error instanceof Error) { | ||
| message = `${message} | ||
| ${error.message}`; | ||
| } | ||
| if (message) { | ||
| throw new import_token_error.VercelOidcTokenError(message); | ||
| } | ||
| throw error; | ||
| } | ||
| return token; | ||
| } | ||
| function getVercelOidcTokenSync() { | ||
| const token = (0, import_get_context.getContext)().headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN; | ||
| if (!token) { | ||
| throw new Error( | ||
| `The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?` | ||
| ); | ||
| } | ||
| return token; | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| getVercelOidcToken, | ||
| getVercelOidcTokenSync | ||
| }); |
| export {}; |
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var import_crypto = require("crypto"); | ||
| var import_vitest = require("vitest"); | ||
| var os = __toESM(require("os")); | ||
| var path = __toESM(require("path")); | ||
| var fs = __toESM(require("fs")); | ||
| var import_token_io = require("./token-io"); | ||
| var import_get_vercel_oidc_token = require("./get-vercel-oidc-token"); | ||
| var tokenUtil = __toESM(require("./token-util")); | ||
| import_vitest.vi.mock("./token-io"); | ||
| import_vitest.vi.mock("./get-context", () => ({ | ||
| getContext: () => ({ headers: {} }) | ||
| })); | ||
| (0, import_vitest.describe)("getVercelOidcToken - Error Scenarios", () => { | ||
| let rootDir; | ||
| let userDataDir; | ||
| let cliDataDir; | ||
| let tokenDataDir; | ||
| const projectId = "prj_test123"; | ||
| const teamId = "team_test456"; | ||
| (0, import_vitest.beforeEach)(() => { | ||
| import_vitest.vi.clearAllMocks(); | ||
| import_vitest.vi.restoreAllMocks(); | ||
| process.env.VERCEL_OIDC_TOKEN = void 0; | ||
| const random = `test-${(0, import_crypto.randomUUID)()}`; | ||
| rootDir = path.join(os.tmpdir(), random); | ||
| userDataDir = path.join(rootDir, "data"); | ||
| cliDataDir = path.join(userDataDir, "com.vercel.cli"); | ||
| tokenDataDir = path.join(userDataDir, "com.vercel.token"); | ||
| fs.mkdirSync(cliDataDir, { recursive: true }); | ||
| fs.mkdirSync(tokenDataDir, { recursive: true }); | ||
| fs.mkdirSync(path.join(rootDir, ".vercel"), { | ||
| recursive: true | ||
| }); | ||
| import_vitest.vi.spyOn(process, "cwd").mockReturnValue(rootDir); | ||
| import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(rootDir); | ||
| import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(userDataDir); | ||
| }); | ||
| (0, import_vitest.afterEach)(() => { | ||
| if (fs.existsSync(rootDir)) { | ||
| fs.rmSync(rootDir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
| (0, import_vitest.test)("should throw AccessTokenMissingError when CLI auth file is missing", async () => { | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /No authentication found/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when project.json is missing", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /project\.json not found, have you linked your project with `vc link\?`/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when root directory cannot be found", async () => { | ||
| import_vitest.vi.mocked(import_token_io.findRootDir).mockReturnValue(null); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /Unable to find project root directory\. Have you linked your project with `vc link\?`/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when user data directory cannot be found", async () => { | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(null); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /Unable to find user data directory\. Please reach out to Vercel support\./ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when API returns non-200", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockRejectedValue( | ||
| new Error("Failed to refresh OIDC token: Unauthorized") | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /Failed to refresh OIDC token: Unauthorized/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when token response is malformed", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockRejectedValue( | ||
| new TypeError( | ||
| "Vercel OIDC token is malformed. Expected a string-valued token property. Please run `vc env pull` and try again" | ||
| ) | ||
| ); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /Vercel OIDC token is malformed\. Expected a string-valued token property\. Please run `vc env pull` and try again/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw helpful error when token has invalid format", async () => { | ||
| process.env.VERCEL_OIDC_TOKEN = "not-a-valid-jwt-token"; | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockImplementation(() => { | ||
| throw new Error("Invalid token. Please run `vc env pull` and try again"); | ||
| }); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /Invalid token\. Please run `vc env pull` and try again/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should throw error when token expiry check fails", async () => { | ||
| process.env.VERCEL_OIDC_TOKEN = "not-a-jwt-token"; | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow( | ||
| /Invalid token\. Please run `vc env pull` and try again/ | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should fail when no token exists and no CLI credentials available", async () => { | ||
| process.env.VERCEL_OIDC_TOKEN = void 0; | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(/Invalid token/); | ||
| }); | ||
| (0, import_vitest.test)("should propagate filesystem errors when saving token fails", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: "new-valid-token" | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "saveToken").mockImplementation(() => { | ||
| throw new Error("EACCES: permission denied"); | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| await (0, import_vitest.expect)((0, import_get_vercel_oidc_token.getVercelOidcToken)()).rejects.toThrow(/EACCES|permission/i); | ||
| }); | ||
| (0, import_vitest.test)("should succeed when valid token exists in env", async () => { | ||
| const validToken = createValidToken(); | ||
| process.env.VERCEL_OIDC_TOKEN = validToken; | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)(); | ||
| (0, import_vitest.expect)(token).toBe(validToken); | ||
| }); | ||
| (0, import_vitest.test)("should refresh when token is expired but all configs are valid", async () => { | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: teamId }) | ||
| ); | ||
| const newToken = createValidToken("new-token"); | ||
| import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)(); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| }); | ||
| (0, import_vitest.test)("should use provided team and project for refresh instead of reading project.json", async () => { | ||
| const customProjectId = "prj_custom123"; | ||
| const customTeamId = "team_custom456"; | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| const newToken = createValidToken("custom-token"); | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({ | ||
| team: customTeamId, | ||
| project: customProjectId | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).toHaveBeenCalledWith( | ||
| "test-auth-token", | ||
| customProjectId, | ||
| customTeamId | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should use provided project and read team from project.json", async () => { | ||
| const customProjectId = "prj_custom234"; | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId: "original-project-id", orgId: teamId }) | ||
| ); | ||
| const newToken = createValidToken("partial-custom-token"); | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({ | ||
| project: customProjectId | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).toHaveBeenCalledWith( | ||
| "test-auth-token", | ||
| customProjectId, | ||
| teamId | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should use provided team and read project from project.json", async () => { | ||
| const customTeamId = "team_custom789"; | ||
| fs.writeFileSync( | ||
| path.join(cliDataDir, "auth.json"), | ||
| JSON.stringify({ token: "test-auth-token" }) | ||
| ); | ||
| fs.writeFileSync( | ||
| path.join(rootDir, ".vercel", "project.json"), | ||
| JSON.stringify({ projectId, orgId: "original-team-id" }) | ||
| ); | ||
| const newToken = createValidToken("partial-custom-token-2"); | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: newToken | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValueOnce({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 - 1e3 | ||
| }).mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| process.env.VERCEL_OIDC_TOKEN = createExpiredToken(); | ||
| const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({ | ||
| team: customTeamId | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(newToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).toHaveBeenCalledWith( | ||
| "test-auth-token", | ||
| projectId, | ||
| customTeamId | ||
| ); | ||
| }); | ||
| (0, import_vitest.test)("should not refresh when token is valid even with options provided", async () => { | ||
| const validToken = createValidToken(); | ||
| process.env.VERCEL_OIDC_TOKEN = validToken; | ||
| const getVercelOidcTokenSpy = import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({ | ||
| token: "should-not-be-called" | ||
| }); | ||
| import_vitest.vi.spyOn(tokenUtil, "getTokenPayload").mockReturnValue({ | ||
| sub: "test-sub", | ||
| name: "test-name", | ||
| exp: Date.now() / 1e3 + 43200 | ||
| }); | ||
| const token = await (0, import_get_vercel_oidc_token.getVercelOidcToken)({ | ||
| team: "custom-team", | ||
| project: "custom-project" | ||
| }); | ||
| (0, import_vitest.expect)(token).toBe(validToken); | ||
| (0, import_vitest.expect)(getVercelOidcTokenSpy).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
| function createExpiredToken() { | ||
| const header = Buffer.from( | ||
| JSON.stringify({ alg: "RS256", typ: "JWT" }) | ||
| ).toString("base64url"); | ||
| const payload = Buffer.from( | ||
| JSON.stringify({ | ||
| sub: "test-sub", | ||
| exp: 1, | ||
| iat: 1 | ||
| }) | ||
| ).toString("base64url"); | ||
| return `${header}.${payload}.fake_signature`; | ||
| } | ||
| function createValidToken(value = "valid-token") { | ||
| const header = Buffer.from( | ||
| JSON.stringify({ alg: "RS256", typ: "JWT" }) | ||
| ).toString("base64url"); | ||
| const payload = Buffer.from( | ||
| JSON.stringify({ | ||
| sub: "test-sub", | ||
| exp: Math.floor(Date.now() / 1e3) + 43200, | ||
| iat: Math.floor(Date.now() / 1e3) | ||
| }) | ||
| ).toString("base64url"); | ||
| return `${header}.${payload}.fake_signature_${value}`; | ||
| } |
107686
4.2%45
9.76%1922
4.68%