hermes/lom/src/commands/users.ts
2024-05-18 13:14:43 -04:00

215 lines
5.6 KiB
TypeScript

import type { Axios } from "axios";
import { SSHCommand } from "../libs/patchCommander.js";
import type { PrintLine, KeyboardRead } from "../commands.js";
type UserLookupSuccess = {
success: true;
data: {
id: number;
isServiceAccount: boolean;
username: string;
name: string;
email: string;
}[];
};
export async function run(
argv: string[],
println: PrintLine,
axios: Axios,
apiKey: string,
readKeyboard: KeyboardRead,
) {
if (argv.length == 1)
return println(
"error: no arguments specified! run %s --help to see commands.\n",
argv[0],
);
const program = new SSHCommand(println);
program.description("Manages users for NextNet");
program.version("v1.0.0");
const addCommand = new SSHCommand(println, "add");
addCommand.description("Create a new user");
addCommand.argument("<username>", "Username of new user");
addCommand.argument("<email>", "Email of new user");
addCommand.argument("[name]", "Name of new user (defaults to username)");
addCommand.option("-p, --password", "Password of User");
addCommand.option(
"-a, --ask-password, --ask-pass, --askpass",
"Asks for a password. Hides output",
);
addCommand.option(
"-s, --service-account, --service",
"Turns the user into a service account",
);
addCommand.action(
async (
username: string,
email: string,
name: string,
options: {
password?: string;
askPassword?: boolean;
isServiceAccount?: boolean;
},
) => {
if (!options.password && !options.askPassword) {
println("No password supplied, and askpass has not been supplied.\n");
return;
}
let password: string = "";
if (options.askPassword) {
let passwordConfirmOne = "a";
let passwordConfirmTwo = "b";
while (passwordConfirmOne != passwordConfirmTwo) {
println("Password: ");
passwordConfirmOne = await readKeyboard(true);
println("\nConfirm password: ");
passwordConfirmTwo = await readKeyboard(true);
println("\n");
if (passwordConfirmOne != passwordConfirmTwo) {
println("Passwords do not match! Try again.\n\n");
}
}
password = passwordConfirmOne;
} else {
// @ts-expect-error: From the first check we do, we know this is safe (you MUST specify a password)
password = options.password;
}
const response = await axios.post("/api/v1/users/create", {
name,
username,
email,
password,
allowUnsafeGlobalTokens: options.isServiceAccount,
});
if (response.status != 200) {
if (process.env.NODE_ENV != "production") console.log(response);
if (response.data.error) {
println(`Error: ${response.data.error}\n`);
} else {
println("Error creating users!\n");
}
return;
}
println("User created successfully.\nToken: %s\n", response.data.token);
},
);
const removeCommand = new SSHCommand(println, "rm");
removeCommand.description("Remove a user");
removeCommand.argument("<uid>", "ID of user to remove");
removeCommand.action(async (uidStr: string) => {
const uid = parseInt(uidStr);
if (Number.isNaN(uid)) {
println("UID (%s) is not a number.\n", uid);
return;
}
const response = await axios.post("/api/v1/users/remove", {
token: apiKey,
uid,
});
if (response.status != 200) {
if (process.env.NODE_ENV != "production") console.log(response);
if (response.data.error) {
println(`Error: ${response.data.error}\n`);
} else {
println("Error deleting user!\n");
}
return;
}
println("User has been successfully deleted.\n");
});
const lookupCommand = new SSHCommand(println, "find");
lookupCommand.description("Find a user");
lookupCommand.option("-i, --id <id>", "UID of User");
lookupCommand.option("-n, --name <name>", "Name of User");
lookupCommand.option("-u, --username <username>", "Username of User");
lookupCommand.option("-e, --email <email>", "Email of User");
lookupCommand.option("-s, --service", "The user is a service account");
lookupCommand.action(async options => {
// FIXME: redundant parseInt calls
if (options.id) {
const uid = parseInt(options.id);
if (Number.isNaN(uid)) {
println("UID (%s) is not a number.\n", uid);
return;
}
}
const response = await axios.post("/api/v1/users/lookup", {
token: apiKey,
id: options.id ? parseInt(options.id) : undefined,
name: options.name,
username: options.username,
email: options.email,
service: Boolean(options.service),
});
if (response.status != 200) {
if (process.env.NODE_ENV != "production") console.log(response);
if (response.data.error) {
println(`Error: ${response.data.error}\n`);
} else {
println("Error finding users!\n");
}
return;
}
const { data }: UserLookupSuccess = response.data;
for (const user of data) {
println(
"UID: %s%s:\n",
user.id,
user.isServiceAccount ? " (service)" : "",
);
println("- Username: %s\n", user.username);
println("- Name: %s\n", user.name);
println("- Email: %s\n", user.email);
println("\n");
}
println("%s users found.\n", data.length);
});
program.addCommand(addCommand);
program.addCommand(removeCommand);
program.addCommand(lookupCommand);
program.parse(argv);
await new Promise(resolve => program.onExit(resolve));
}