@curvenote/cli
Version:
CLI Client library for Curvenote
121 lines (120 loc) • 4.95 kB
JavaScript
import fs from 'node:fs';
import path from 'node:path';
import jwt from 'jsonwebtoken';
import { chalkLogger, LogLevel } from 'myst-cli-utils';
import { getConfigPath } from './utils/index.js';
export function decodeTokenAndCheckExpiry(token, log, throwErrors = true, kindForExpiryCheck = 'session') {
const rawDecoded = jwt.decode(token);
if (!rawDecoded || typeof rawDecoded === 'string')
throw new Error('Could not decode session token. Please ensure that the API token is valid.');
const decoded = rawDecoded;
const timeLeft = decoded.exp * 1000 - Date.now();
if (!decoded.ignoreExpiration && timeLeft < 0) {
if (throwErrors) {
throw new Error('The API token has expired. You can remove your token using: `curvenote token remove`');
}
return { decoded, expired: true };
}
if (!decoded.ignoreExpiration) {
if (kindForExpiryCheck === 'session' && timeLeft < 30 * 1000) {
if (throwErrors)
log.warn(`The token has less than 30 seconds remaining`);
return { decoded, expired: 'soon' };
}
if (kindForExpiryCheck === 'user' && timeLeft < 24 * 60 * 60 * 1000) {
if (throwErrors)
log.warn(`The token has less than 1 day remaining`);
return { decoded, expired: 'soon' };
}
}
return { decoded, expired: false };
}
export function validateSessionToken(token, log) {
const { decoded } = decodeTokenAndCheckExpiry(token, log);
const { aud, cfg, iss } = decoded;
if (typeof aud !== 'string')
throw new Error('Expected an audience on the token (string).');
if (!(iss === null || iss === void 0 ? void 0 : iss.endsWith('tokens/session')))
throw new Error('Expected a session token.');
if (typeof cfg === 'string')
log.debug(`SessionToken contains a "cfg" claim, reading configuration from api at ${cfg}.`);
return { token, decoded };
}
/**
* Return `current` token and `saved` available tokens
*
* Curvenote tokens can come from 2 places:
* - CURVENOTE_TOKEN environment variable
* - Curvenote config file saved to your system
*
* The curvenote config may have a list of available tokens and a current token;
* this function will return the available tokens as `saved` and the `current` token.
*
* If CURVENOTE_TOKEN environment variable is found, it will be returned as `current`,
* taking priority over any current token in your config file. The field `environment`
* will be set to `true`, indicating current came from the environment variable.
*/
export function getTokens(log = chalkLogger(LogLevel.info, process.cwd())) {
const env = process.env.CURVENOTE_TOKEN;
if (env) {
log.warn('Using the CURVENOTE_TOKEN env variable.');
}
const configPath = getConfigPath();
let config;
if (fs.existsSync(configPath)) {
try {
config = JSON.parse(fs.readFileSync(configPath).toString());
}
catch (error) {
log.debug(`\n\n${error === null || error === void 0 ? void 0 : error.stack}\n\n`);
if (env) {
log.error('Could not read settings file; continuing with CURVENOTE_TOKEN env variable');
}
else {
throw new Error('Could not read settings file to get Curvenote token');
}
}
}
return {
saved: config === null || config === void 0 ? void 0 : config.tokens,
current: env !== null && env !== void 0 ? env : config === null || config === void 0 ? void 0 : config.token,
environment: !!env,
};
}
/**
* Write token config data to file
*/
export function writeConfigFile(data) {
const configPath = getConfigPath();
if (!fs.existsSync(configPath)) {
fs.mkdirSync(path.dirname(configPath), { recursive: true });
}
fs.writeFileSync(configPath, JSON.stringify(data));
}
/**
* Replace current token in saved config file
*/
export function updateCurrentTokenConfig(log, token) {
const { saved } = getTokens();
writeConfigFile({ tokens: saved, token });
}
export function summarizeAsString({ note, username, email, api }) {
return `"${username}" <${email}> at ${api}${note ? ` "${note}"` : ''}`;
}
export function getCurrentTokenRecord(tokens) {
var _a, _b;
const data = tokens !== null && tokens !== void 0 ? tokens : getTokens();
if (!data.current)
return;
if (data.environment) {
const { decoded } = decodeTokenAndCheckExpiry(data.current, chalkLogger(LogLevel.info));
return {
token: data.current,
api: decoded.aud,
email: decoded.email,
username: (_a = decoded.name) !== null && _a !== void 0 ? _a : decoded.user_id,
note: 'From environment variable',
};
}
return (_b = data.saved) === null || _b === void 0 ? void 0 : _b.find(({ token }) => token === data.current);
}