74 lines
2.5 KiB
TypeScript
74 lines
2.5 KiB
TypeScript
import crypto from "node:crypto";
|
|||
|
|
import path from "node:path";
|
||
|
|
import { password as promptPassword } from "@inquirer/prompts";
|
||
|
|
import { stringify } from "yaml";
|
||
|
|
import { hashPassword } from "../../lib/auth/password";
|
||
|
|
import { upsertEnvVar } from "../lib/env-file";
|
||
|
|
import { resolveConfigPath } from "../lib/paths";
|
||
|
|
import { slug } from "../lib/slug";
|
||
|
|
|
||
|
|
export interface UsersOptions {
|
||
|
|
config?: string;
|
||
|
|
inline?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
function printSnippet(heading: string, entry: Record<string, unknown>): void {
|
||
|
|
console.log(`\n${heading}\n`);
|
||
|
|
console.log(stringify([entry]));
|
||
|
|
}
|
||
|
|
|
||
|
|
async function promptNewPassword(): Promise<string> {
|
||
|
|
for (;;) {
|
||
|
|
const first = await promptPassword({ message: "Password", mask: true });
|
||
|
|
const second = await promptPassword({ message: "Confirm password", mask: true });
|
||
|
|
if (first === second) return first;
|
||
|
|
console.error("Passwords did not match, try again.\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function usersAddCommand(username: string, opts: UsersOptions): Promise<void> {
|
||
|
|
const password = await promptNewPassword();
|
||
|
|
const passwordHash = await hashPassword(password);
|
||
|
|
|
||
|
|
if (opts.inline) {
|
||
|
|
printSnippet("Add this under `auth.users:` in your config file:", { username, passwordHash });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const configPath = resolveConfigPath(opts.config);
|
||
|
|
const envPath = path.join(path.dirname(configPath), ".env");
|
||
|
|
const varName = `TRIGGERSHELL_USER_${slug(username)}_PASSWORD_HASH`;
|
||
|
|
upsertEnvVar(envPath, varName, passwordHash);
|
||
|
|
|
||
|
|
console.log(`Stored ${varName} in ${envPath}`);
|
||
|
|
printSnippet("Add this under `auth.users:` in your config file:", {
|
||
|
|
username,
|
||
|
|
passwordHash: `\${${varName}}`,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function usersAddTokenCommand(name: string, opts: UsersOptions): Promise<void> {
|
||
|
|
const token = crypto.randomBytes(32).toString("hex");
|
||
|
|
const tokenHash = `sha256:${crypto.createHash("sha256").update(token).digest("hex")}`;
|
||
|
|
|
||
|
|
console.log("\nSave this token now - it will not be shown again:");
|
||
|
|
console.log(` ${token}`);
|
||
|
|
console.log(`Use it as: Authorization: Bearer ${token}`);
|
||
|
|
|
||
|
|
if (opts.inline) {
|
||
|
|
printSnippet("Add this under `auth.tokens:` in your config file:", { name, tokenHash });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const configPath = resolveConfigPath(opts.config);
|
||
|
|
const envPath = path.join(path.dirname(configPath), ".env");
|
||
|
|
const varName = `TRIGGERSHELL_TOKEN_${slug(name)}_HASH`;
|
||
|
|
upsertEnvVar(envPath, varName, tokenHash);
|
||
|
|
|
||
|
|
console.log(`Stored ${varName} in ${envPath}`);
|
||
|
|
printSnippet("Add this under `auth.tokens:` in your config file:", {
|
||
|
|
name,
|
||
|
|
tokenHash: `\${${varName}}`,
|
||
|
|
});
|
||
|
|
}
|