replit-identity
Version:
A simple package to interact with the Replit CLI's identity command.
126 lines (114 loc) • 4.48 kB
JavaScript
const { exec } = require('child_process');
const util = require('node:util');
const execAsync = util.promisify(exec);
const { REPLIT_CLI, REPLIT_DEPLOYMENT, REPL_IDENTITY } = process.env;
if (!REPLIT_CLI || !REPL_IDENTITY) {
const deployment = REPLIT_DEPLOYMENT === '1';
if (deployment) {
throw 'Replit Identity is not yet available in deployments.';
} else {
throw 'Replit Identity is not available in this environment.';
}
}
/**
* @typedef {Object} Runtime
* @property {Object} [interactive]
* This is set if the Repl is running interactively and not when the Repl is running in hosting.
* @property {string} interactive.cluster - The cluster in which this Repl is running.
* @property {string} interactive.subcluster - The subcluster in which this Repl is running.
* @property {Object} [hosting]
* This is set if the Repl is running in a hosting subcluster.
* @property {string} hosting.cluster - The cluster in which this Repl is running.
* @property {string} hosting.subcluster - The subcluster in which this Repl is running.
* @property {boolean} [deployment]
* This is set if the Repl is running in a Deployment.
*/
/**
* @typedef {Object} Info
* @property {string} username - The name of the user who created the token.
* @property {string} slug - The slug of the repl where the token was created.
* @property {number} userId - The id of the user who created the token.
* @property {string} replId - The id of the repl where the token was created.
* @property {string} [originReplId] - The id of the original repl despite the running environment being a fork.
* @property {string} audience - The audience for which the token was created.
* @property {Runtime|null} runtime - Runtime information about the Repl.
*/
/**
* Executes Replit CLI's identity command with specified command and arguments.
* @function
* @param {'create'|'verify'} cmd - The command to execute.
* @param {Object} [flags] - An object containing the options for the command.
* @param {string} flags.audience - The audience for which the token will be/was created.
* @param {string} flags.token - The token to verify. (Used with verify command)
* @param {string} flags.json - Returns object.
* @returns {Promise<object|string|null>} - Resolves with the result, or rejects on error.
*/
async function identity(cmd = '', flags = {}) {
try {
let args = '';
const keys = Object.keys(flags);
for (let i = 0; i < keys.length; i++) {
const flag = keys[i];
const value = flags[flag];
if (!value) throw `${flag} is required.`;
if (typeof value !== 'string') throw `${flag} must be a string.`;
args += ` -${flag}="${value.replaceAll('"', '\\"')}"`
}
const command = `${REPLIT_CLI} identity ${cmd}` + args;
const result = await execAsync(command);
const output = result.stdout.trim();
return flags.json ? JSON.parse(output) : output;
} catch (err) {
if (typeof err === 'string') {
throw `[Replit Identity] ${cmd}() -> ${err}`;
} else {
return null; // error is identity mismatch or invalid token
}
}
}
/**
* Creates a new identity token.
* @function
* @param {string} audience - The audience for which the token will be created.
* @returns {Promise<string|null>} - Resolves with the token, or rejects on error.
*/
async function create(audience) {
return identity('create', { audience });
}
/**
* Verifies an existing identity token against an audience.
* @function
* @param {string} token - The identity token to verify.
* @param {string} audience - The audience for which the token was created.
* @returns {Promise<Info|null>} - Resolves with the Info object, or rejects on error.
*/
async function verify(token, audience) {
const info = await identity('verify', {
token, audience, json: 'true'
});
return info ? camelize(info) : null;
}
module.exports = { create, verify };
function camelize(obj) {
rename(obj, {
replId: 'replid',
originReplId: 'originReplid',
username: 'user',
userId: 'user_id',
audience: 'aud',
runtime: 'Runtime'
});
rename(obj.runtime, {
interactive: 'Interactive',
hosting: 'Hosting',
deployment: 'Deployment',
});
return obj;
}
// supposedly the fastest way to do this
function rename(object, keys) {
for (const [newKey, oldKey] of Object.entries(keys)) {
if (oldKey in object)
delete Object.assign(object, { [newKey]: object[oldKey] })[oldKey];
}
}