diff --git a/api/src/index.ts b/api/src/index.ts index 4e16bf7..fb9e39e 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -8,6 +8,7 @@ import type { SessionToken, RouteOptions, } from "./libs/types.js"; + import type { BackendBaseClass } from "./backendimpl/base.js"; import { route as getPermissions } from "./routes/getPermissions.js"; diff --git a/api/src/routes/user/lookup.ts b/api/src/routes/user/lookup.ts index 02e3093..d2c8473 100644 --- a/api/src/routes/user/lookup.ts +++ b/api/src/routes/user/lookup.ts @@ -60,6 +60,7 @@ export function route(routeOptions: RouteOptions) { return { success: true, data: users.map(i => ({ + id: i.id, name: i.name, email: i.email, isServiceAccount: i.isRootServiceAccount, diff --git a/lom/src/commands.ts b/lom/src/commands.ts index 41cc586..19448fa 100644 --- a/lom/src/commands.ts +++ b/lom/src/commands.ts @@ -5,12 +5,14 @@ import { run as backends } from "./commands/backends.js"; import { run as users } from "./commands/users.js"; export type PrintLine = (...str: any[]) => void; +export type KeyboardRead = (disableEcho?: boolean) => Promise; type Command = ( args: string[], println: PrintLine, axios: Axios, apiKey: string, + keyboardRead: KeyboardRead ) => Promise; type Commands = { diff --git a/lom/src/commands/backends.ts b/lom/src/commands/backends.ts index b614874..453e65f 100644 --- a/lom/src/commands/backends.ts +++ b/lom/src/commands/backends.ts @@ -88,4 +88,5 @@ export async function run( program.addCommand(lookupBackend); program.parse(argv); + await new Promise((resolve) => program.onExit(resolve)); } diff --git a/lom/src/commands/users.ts b/lom/src/commands/users.ts index b994893..4d6badf 100644 --- a/lom/src/commands/users.ts +++ b/lom/src/commands/users.ts @@ -1,17 +1,29 @@ import type { Axios } from "axios"; import { SSHCommand } from "../libs/patchCommander.js"; -import type { PrintLine } from "../commands.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 ) { const program = new SSHCommand(println); program.description("Manages users for NextNet"); - program.version("v0.1.0-preprod"); + program.version("v1.0.0-testing"); const addCommand = new SSHCommand(println, "add"); addCommand.description("Create a new user"); @@ -25,21 +37,155 @@ export async function run( "Asks for a password. Hides output", ); + addCommand.action(async(username: string, email: string, name: string, options: { + password?: string, + askPassword?: 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 { + // From the first check we do, we know this is safe (you MUST specify a password) + // @ts-ignore + password = options.password; + } + + const response = await axios.post("/api/v1/users/create", { + name, + username, + email, + password + }); + + 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 requesting connections!\n"); + } + + return; + } + + println("User created successfully.\n"); + }) + const removeCommand = new SSHCommand(println, "rm"); removeCommand.description("Remove a user"); removeCommand.argument("", "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; + } + + let 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 ", "UID of User"); lookupCommand.option("-n, --name ", "Name of User"); lookupCommand.option("-u, --username ", "Username of User"); lookupCommand.option("-e, --email ", "Email of User"); - lookupCommand.option("-s, --service", "The User is a service account"); + 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 user!\n"); + } + + return; + } + + const { data }: UserLookupSuccess = response.data; + + data.forEach((user) => { + 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", response.data.data.length); + }) program.addCommand(addCommand); program.addCommand(removeCommand); program.addCommand(lookupCommand); program.parse(argv); + await new Promise((resolve) => program.onExit(resolve)); } diff --git a/lom/src/index.ts b/lom/src/index.ts index 2ea5e13..b623411 100644 --- a/lom/src/index.ts +++ b/lom/src/index.ts @@ -108,8 +108,8 @@ server.on("connection", client => { continue; } - await command.run(argv, println, axios, token); - stream.write(`~$ `); + await command.run(argv, println, axios, token, (disableEcho) => readFromKeyboard(stream, disableEcho)); + stream.write("~$ "); } } }); diff --git a/lom/src/libs/patchCommander.ts b/lom/src/libs/patchCommander.ts index e6cc60f..2c2aa61 100644 --- a/lom/src/libs/patchCommander.ts +++ b/lom/src/libs/patchCommander.ts @@ -25,7 +25,7 @@ export class SSHCommand extends Command { this.configureOutput({ writeOut: str => println(str), writeErr: str => { - if (str.includes("--help") || str.includes("-h")) return; + if (this.hasRecievedExitSignal) return; println(str); }, });