🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@vercel/oidc

Package Overview
Dependencies
Maintainers
4
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vercel/oidc - npm Package Compare versions

Comparing version
3.0.5
to
3.1.0
+30
dist/auth-config.d.ts
/**
* Auth configuration stored in ~/.../com.vercel.cli/auth.json
*/
export interface AuthConfig {
/** An `access_token` obtained using the OAuth Device Authorization flow. */
token?: string;
/** A `refresh_token` obtained using the OAuth Device Authorization flow. */
refreshToken?: string;
/**
* The absolute time (seconds) when the token expires.
* Used to optimistically check if the token is still valid.
*/
expiresAt?: number;
/** Whether to skip writing this config to disk. */
skipWrite?: boolean;
}
/**
* Read the auth config from disk
* Returns null if the file doesn't exist or cannot be read
*/
export declare function readAuthConfig(): AuthConfig | null;
/**
* Write the auth config to disk with proper permissions
*/
export declare function writeAuthConfig(config: AuthConfig): void;
/**
* Check if an access token is valid (not expired)
* Copied from packages/cli/src/util/client.ts:72-81
*/
export declare function isValidAccessToken(authConfig: AuthConfig): boolean;
"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 auth_config_exports = {};
__export(auth_config_exports, {
isValidAccessToken: () => isValidAccessToken,
readAuthConfig: () => readAuthConfig,
writeAuthConfig: () => writeAuthConfig
});
module.exports = __toCommonJS(auth_config_exports);
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var import_token_util = require("./token-util");
function getAuthConfigPath() {
const dataDir = (0, import_token_util.getVercelDataDir)();
if (!dataDir) {
throw new Error(
`Unable to find Vercel CLI data directory. Your platform: ${process.platform}. Supported: darwin, linux, win32.`
);
}
return path.join(dataDir, "auth.json");
}
function readAuthConfig() {
try {
const authPath = getAuthConfigPath();
if (!fs.existsSync(authPath)) {
return null;
}
const content = fs.readFileSync(authPath, "utf8");
if (!content) {
return null;
}
return JSON.parse(content);
} catch (error) {
return null;
}
}
function writeAuthConfig(config) {
const authPath = getAuthConfigPath();
const authDir = path.dirname(authPath);
if (!fs.existsSync(authDir)) {
fs.mkdirSync(authDir, { mode: 504, recursive: true });
}
fs.writeFileSync(authPath, JSON.stringify(config, null, 2), { mode: 384 });
}
function isValidAccessToken(authConfig) {
if (!authConfig.token)
return false;
if (typeof authConfig.expiresAt !== "number")
return true;
const nowInSeconds = Math.floor(Date.now() / 1e3);
return authConfig.expiresAt >= nowInSeconds;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
isValidAccessToken,
readAuthConfig,
writeAuthConfig
});
"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 = "test-project-id";
const teamId = "test-team-id";
(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 helpful error 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(
/Failed to refresh OIDC token: Log in to Vercel CLI and link your project with `vc link`/
);
});
(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);
});
});
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 interface TokenSet {
access_token: string;
token_type: 'Bearer';
expires_in: number;
refresh_token?: string;
scope?: string;
}
/**
* Refresh an OAuth access token using a refresh token
*/
export declare function refreshTokenRequest(options: {
refresh_token: string;
}): Promise<Response>;
/**
* Process the token response and extract the token set
*/
export declare function processTokenResponse(response: Response): Promise<[Error] | [null, TokenSet]>;
"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 oauth_exports = {};
__export(oauth_exports, {
processTokenResponse: () => processTokenResponse,
refreshTokenRequest: () => refreshTokenRequest
});
module.exports = __toCommonJS(oauth_exports);
var import_os = require("os");
const VERCEL_ISSUER = "https://vercel.com";
const VERCEL_CLI_CLIENT_ID = "cl_HYyOPBNtFMfHhaUn9L4QPfTZz6TP47bp";
const userAgent = `@vercel/oidc node-${process.version} ${(0, import_os.platform)()} (${(0, import_os.arch)()}) ${(0, import_os.hostname)()}`;
let _tokenEndpoint = null;
async function getTokenEndpoint() {
if (_tokenEndpoint) {
return _tokenEndpoint;
}
const discoveryUrl = `${VERCEL_ISSUER}/.well-known/openid-configuration`;
const response = await fetch(discoveryUrl, {
headers: { "user-agent": userAgent }
});
if (!response.ok) {
throw new Error("Failed to discover OAuth endpoints");
}
const metadata = await response.json();
if (!metadata || typeof metadata.token_endpoint !== "string") {
throw new Error("Invalid OAuth discovery response");
}
const endpoint = metadata.token_endpoint;
_tokenEndpoint = endpoint;
return endpoint;
}
async function refreshTokenRequest(options) {
const tokenEndpoint = await getTokenEndpoint();
return await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"user-agent": userAgent
},
body: new URLSearchParams({
client_id: VERCEL_CLI_CLIENT_ID,
grant_type: "refresh_token",
...options
})
});
}
async function processTokenResponse(response) {
const json = await response.json();
if (!response.ok) {
const errorMsg = typeof json === "object" && json && "error" in json ? String(json.error) : "Token refresh failed";
return [new Error(errorMsg)];
}
if (typeof json !== "object" || json === null) {
return [new Error("Invalid token response")];
}
if (typeof json.access_token !== "string") {
return [new Error("Missing access_token in response")];
}
if (json.token_type !== "Bearer") {
return [new Error("Invalid token_type in response")];
}
if (typeof json.expires_in !== "number") {
return [new Error("Missing expires_in in response")];
}
return [null, json];
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
processTokenResponse,
refreshTokenRequest
});
"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_vitest = require("vitest");
var import_token_util = require("./token-util");
var authConfig = __toESM(require("./auth-config"));
var oauth = __toESM(require("./oauth"));
import_vitest.vi.mock("fs");
import_vitest.vi.mock("./token-io", () => ({
getUserDataDir: import_vitest.vi.fn(() => "/mock/user/data"),
findRootDir: import_vitest.vi.fn(() => "/mock/root")
}));
(0, import_vitest.describe)("getVercelCliToken", () => {
(0, import_vitest.beforeEach)(() => {
import_vitest.vi.clearAllMocks();
});
(0, import_vitest.afterEach)(() => {
import_vitest.vi.restoreAllMocks();
});
(0, import_vitest.it)("should return token if valid and not expired", async () => {
const validToken = {
token: "valid-access-token",
refreshToken: "refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) + 3600
// expires in 1 hour
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(validToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBe("valid-access-token");
(0, import_vitest.expect)(authConfig.writeAuthConfig).not.toHaveBeenCalled();
});
(0, import_vitest.it)("should return null if auth config does not exist", async () => {
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(null);
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBeNull();
});
(0, import_vitest.it)("should refresh token if expired and refresh token exists", async () => {
const expiredToken = {
token: "expired-access-token",
refreshToken: "valid-refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
// expired 1 hour ago
};
const mockResponse = {
ok: true,
json: async () => ({
access_token: "new-access-token",
token_type: "Bearer",
expires_in: 3600,
refresh_token: "new-refresh-token"
})
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBe("new-access-token");
(0, import_vitest.expect)(oauth.refreshTokenRequest).toHaveBeenCalledWith({
refresh_token: "valid-refresh-token"
});
(0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith(
import_vitest.expect.objectContaining({
token: "new-access-token",
refreshToken: "new-refresh-token",
expiresAt: import_vitest.expect.any(Number)
})
);
});
(0, import_vitest.it)("should clear auth and return null if token expired and no refresh token", async () => {
const expiredTokenNoRefresh = {
token: "expired-access-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(
expiredTokenNoRefresh
);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBeNull();
(0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith({});
});
(0, import_vitest.it)("should clear auth if refresh fails with OAuth error", async () => {
const expiredToken = {
token: "expired-access-token",
refreshToken: "invalid-refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
};
const mockErrorResponse = {
ok: false,
json: async () => ({
error: "invalid_grant",
error_description: "Refresh token expired"
})
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockErrorResponse);
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBeNull();
(0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith({});
});
(0, import_vitest.it)("should clear auth if refresh fails with network error", async () => {
const expiredToken = {
token: "expired-access-token",
refreshToken: "valid-refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockRejectedValue(
new Error("Network error")
);
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBeNull();
(0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith({});
});
(0, import_vitest.it)("should treat token as valid if expiresAt is missing (--token case)", async () => {
const tokenWithoutExpiry = {
token: "cli-provided-token"
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(tokenWithoutExpiry);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
const token = await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(token).toBe("cli-provided-token");
(0, import_vitest.expect)(authConfig.writeAuthConfig).not.toHaveBeenCalled();
});
(0, import_vitest.it)("should preserve new refresh token if provided in response", async () => {
const expiredToken = {
token: "expired-access-token",
refreshToken: "old-refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
};
const mockResponse = {
ok: true,
json: async () => ({
access_token: "new-access-token",
token_type: "Bearer",
expires_in: 3600,
refresh_token: "new-refresh-token"
})
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
await (0, import_token_util.getVercelCliToken)();
(0, import_vitest.expect)(authConfig.writeAuthConfig).toHaveBeenCalledWith(
import_vitest.expect.objectContaining({
refreshToken: "new-refresh-token"
})
);
});
(0, import_vitest.it)("should not overwrite refresh token if not provided in response", async () => {
const expiredToken = {
token: "expired-access-token",
refreshToken: "existing-refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
};
const mockResponse = {
ok: true,
json: async () => ({
access_token: "new-access-token",
token_type: "Bearer",
expires_in: 3600
// No refresh_token in response
})
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
await (0, import_token_util.getVercelCliToken)();
const writeCall = import_vitest.vi.mocked(authConfig.writeAuthConfig).mock.calls[0][0];
(0, import_vitest.expect)(writeCall).not.toHaveProperty("refreshToken");
});
(0, import_vitest.it)("should calculate expiresAt correctly from expires_in", async () => {
const expiredToken = {
token: "expired-access-token",
refreshToken: "valid-refresh-token",
expiresAt: Math.floor(Date.now() / 1e3) - 3600
};
const mockResponse = {
ok: true,
json: async () => ({
access_token: "new-access-token",
token_type: "Bearer",
expires_in: 7200
// 2 hours
})
};
import_vitest.vi.spyOn(authConfig, "readAuthConfig").mockReturnValue(expiredToken);
import_vitest.vi.spyOn(authConfig, "writeAuthConfig").mockImplementation(() => {
});
import_vitest.vi.spyOn(oauth, "refreshTokenRequest").mockResolvedValue(mockResponse);
const beforeCall = Math.floor(Date.now() / 1e3);
await (0, import_token_util.getVercelCliToken)();
const afterCall = Math.floor(Date.now() / 1e3);
const writeCall = import_vitest.vi.mocked(authConfig.writeAuthConfig).mock.calls[0][0];
(0, import_vitest.expect)(writeCall.expiresAt).toBeGreaterThanOrEqual(beforeCall + 7200);
(0, import_vitest.expect)(writeCall.expiresAt).toBeLessThanOrEqual(afterCall + 7200);
});
});
+10
-0
# @vercel/oidc
## 3.1.0
### Minor Changes
- Allow vercel/oidc to refresh the vercel CLI auth token when running locally ([#14543](https://github.com/vercel/vercel/pull/14543))
### Patch Changes
- improve error messages for package consumers ([#14449](https://github.com/vercel/vercel/pull/14449))
## 3.0.5

@@ -4,0 +14,0 @@

+7
-3

@@ -55,7 +55,11 @@ "use strict";

} catch (error) {
if (err?.message && error instanceof Error) {
error.message = `${err.message}
let message = err instanceof Error ? err.message : "";
if (error instanceof Error) {
message = `${message}
${error.message}`;
}
throw new import_token_error.VercelOidcTokenError(`Failed to refresh OIDC token`, error);
if (message) {
throw new import_token_error.VercelOidcTokenError(message);
}
throw error;
}

@@ -62,0 +66,0 @@ return token;

@@ -1,2 +0,2 @@

export declare function findRootDir(): string;
export declare function findRootDir(): string | null;
export declare function getUserDataDir(): string | null;

@@ -54,3 +54,3 @@ "use strict";

}
throw new import_token_error.VercelOidcTokenError("Unable to find root directory");
return null;
}

@@ -57,0 +57,0 @@ function getUserDataDir() {

export declare function getVercelDataDir(): string | null;
export declare function getVercelCliToken(): string | null;
export declare function getVercelCliToken(): Promise<string | null>;
interface VercelTokenResponse {

@@ -4,0 +4,0 @@ token: string;

@@ -46,2 +46,4 @@ "use strict";

var import_token_io = require("./token-io");
var import_auth_config = require("./auth-config");
var import_oauth = require("./oauth");
function getVercelDataDir() {

@@ -55,44 +57,64 @@ const vercelFolder = "com.vercel.cli";

}
function getVercelCliToken() {
const dataDir = getVercelDataDir();
if (!dataDir) {
async function getVercelCliToken() {
const authConfig = (0, import_auth_config.readAuthConfig)();
if (!authConfig) {
return null;
}
const tokenPath = path.join(dataDir, "auth.json");
if (!fs.existsSync(tokenPath)) {
if ((0, import_auth_config.isValidAccessToken)(authConfig)) {
return authConfig.token || null;
}
if (!authConfig.refreshToken) {
(0, import_auth_config.writeAuthConfig)({});
return null;
}
const token = fs.readFileSync(tokenPath, "utf8");
if (!token) {
try {
const tokenResponse = await (0, import_oauth.refreshTokenRequest)({
refresh_token: authConfig.refreshToken
});
const [tokensError, tokens] = await (0, import_oauth.processTokenResponse)(tokenResponse);
if (tokensError || !tokens) {
(0, import_auth_config.writeAuthConfig)({});
return null;
}
const updatedConfig = {
token: tokens.access_token,
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
};
if (tokens.refresh_token) {
updatedConfig.refreshToken = tokens.refresh_token;
}
(0, import_auth_config.writeAuthConfig)(updatedConfig);
return updatedConfig.token ?? null;
} catch (error) {
(0, import_auth_config.writeAuthConfig)({});
return null;
}
return JSON.parse(token).token;
}
async function getVercelOidcToken(authToken, projectId, teamId) {
try {
const url = `https://api.vercel.com/v1/projects/${projectId}/token?source=vercel-oidc-refresh${teamId ? `&teamId=${teamId}` : ""}`;
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${authToken}`
}
});
if (!res.ok) {
throw new import_token_error.VercelOidcTokenError(
`Failed to refresh OIDC token: ${res.statusText}`
);
const url = `https://api.vercel.com/v1/projects/${projectId}/token?source=vercel-oidc-refresh${teamId ? `&teamId=${teamId}` : ""}`;
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${authToken}`
}
const tokenRes = await res.json();
assertVercelOidcTokenResponse(tokenRes);
return tokenRes;
} catch (e) {
throw new import_token_error.VercelOidcTokenError(`Failed to refresh OIDC token`, e);
});
if (!res.ok) {
throw new import_token_error.VercelOidcTokenError(
`Failed to refresh OIDC token: ${res.statusText}`
);
}
const tokenRes = await res.json();
assertVercelOidcTokenResponse(tokenRes);
return tokenRes;
}
function assertVercelOidcTokenResponse(res) {
if (!res || typeof res !== "object") {
throw new TypeError("Expected an object");
throw new TypeError(
"Vercel OIDC token is malformed. Expected an object. Please run `vc env pull` and try again"
);
}
if (!("token" in res) || typeof res.token !== "string") {
throw new TypeError("Expected a string-valued token property");
throw new TypeError(
"Vercel OIDC token is malformed. Expected a string-valued token property. Please run `vc env pull` and try again"
);
}

@@ -103,50 +125,48 @@ }

if (!dir) {
throw new import_token_error.VercelOidcTokenError("Unable to find root directory");
throw new import_token_error.VercelOidcTokenError(
"Unable to find project root directory. Have you linked your project with `vc link?`"
);
}
try {
const prjPath = path.join(dir, ".vercel", "project.json");
if (!fs.existsSync(prjPath)) {
throw new import_token_error.VercelOidcTokenError("project.json not found");
}
const prj = JSON.parse(fs.readFileSync(prjPath, "utf8"));
if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") {
throw new TypeError("Expected a string-valued projectId property");
}
return { projectId: prj.projectId, teamId: prj.orgId };
} catch (e) {
throw new import_token_error.VercelOidcTokenError(`Unable to find project ID`, e);
const prjPath = path.join(dir, ".vercel", "project.json");
if (!fs.existsSync(prjPath)) {
throw new import_token_error.VercelOidcTokenError(
"project.json not found, have you linked your project with `vc link?`"
);
}
const prj = JSON.parse(fs.readFileSync(prjPath, "utf8"));
if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") {
throw new TypeError(
"Expected a string-valued projectId property. Try running `vc link` to re-link your project."
);
}
return { projectId: prj.projectId, teamId: prj.orgId };
}
function saveToken(token, projectId) {
try {
const dir = (0, import_token_io.getUserDataDir)();
if (!dir) {
throw new import_token_error.VercelOidcTokenError("Unable to find user data directory");
}
const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
const tokenJson = JSON.stringify(token);
fs.mkdirSync(path.dirname(tokenPath), { mode: 504, recursive: true });
fs.writeFileSync(tokenPath, tokenJson);
fs.chmodSync(tokenPath, 432);
return;
} catch (e) {
throw new import_token_error.VercelOidcTokenError(`Failed to save token`, e);
const dir = (0, import_token_io.getUserDataDir)();
if (!dir) {
throw new import_token_error.VercelOidcTokenError(
"Unable to find user data directory. Please reach out to Vercel support."
);
}
const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
const tokenJson = JSON.stringify(token);
fs.mkdirSync(path.dirname(tokenPath), { mode: 504, recursive: true });
fs.writeFileSync(tokenPath, tokenJson);
fs.chmodSync(tokenPath, 432);
return;
}
function loadToken(projectId) {
try {
const dir = (0, import_token_io.getUserDataDir)();
if (!dir) {
return null;
}
const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
if (!fs.existsSync(tokenPath)) {
return null;
}
const token = JSON.parse(fs.readFileSync(tokenPath, "utf8"));
assertVercelOidcTokenResponse(token);
return token;
} catch (e) {
throw new import_token_error.VercelOidcTokenError(`Failed to load token`, e);
const dir = (0, import_token_io.getUserDataDir)();
if (!dir) {
throw new import_token_error.VercelOidcTokenError(
"Unable to find user data directory. Please reach out to Vercel support."
);
}
const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
if (!fs.existsSync(tokenPath)) {
return null;
}
const token = JSON.parse(fs.readFileSync(tokenPath, "utf8"));
assertVercelOidcTokenResponse(token);
return token;
}

@@ -156,3 +176,5 @@ function getTokenPayload(token) {

if (tokenParts.length !== 3) {
throw new import_token_error.VercelOidcTokenError("Invalid token");
throw new import_token_error.VercelOidcTokenError(
"Invalid token. Please run `vc env pull` and try again"
);
}

@@ -159,0 +181,0 @@ const base64 = tokenParts[1].replace(/-/g, "+").replace(/_/g, "/");

@@ -30,6 +30,6 @@ "use strict";

if (!maybeToken || (0, import_token_util.isExpired)((0, import_token_util.getTokenPayload)(maybeToken.token))) {
const authToken = (0, import_token_util.getVercelCliToken)();
const authToken = await (0, import_token_util.getVercelCliToken)();
if (!authToken) {
throw new import_token_error.VercelOidcTokenError(
"Failed to refresh OIDC token: login to vercel cli"
"Failed to refresh OIDC token: Log in to Vercel CLI and link your project with `vc link`"
);

@@ -39,3 +39,3 @@ }

throw new import_token_error.VercelOidcTokenError(
"Failed to refresh OIDC token: project id not found"
"Failed to refresh OIDC token: Try re-linking your project with `vc link`"
);

@@ -42,0 +42,0 @@ }

@@ -60,3 +60,3 @@ "use strict";

import_vitest.vi.mocked(import_token_io.getUserDataDir).mockReturnValue(userDataDir);
import_vitest.vi.spyOn(tokenUtil, "getVercelCliToken").mockReturnValue("test");
import_vitest.vi.spyOn(tokenUtil, "getVercelCliToken").mockResolvedValue("test");
import_vitest.vi.spyOn(tokenUtil, "getVercelOidcToken").mockResolvedValue({

@@ -63,0 +63,0 @@ token: "test-token"

@@ -104,2 +104,2 @@ # @vercel/oidc

[get-vercel-oidc-token.ts:81](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token.ts#L81)
[get-vercel-oidc-token.ts:85](https://github.com/vercel/vercel/blob/main/packages/oidc/src/get-vercel-oidc-token.ts#L85)

@@ -20,3 +20,3 @@ {

},
"version": "3.0.5",
"version": "3.1.0",
"repository": {

@@ -23,0 +23,0 @@ "directory": "packages/oidc",