Files
kuber/command/auth.ts
T
2026-09-03 11:28:30 +07:00

137 lines
3.2 KiB
TypeScript

import { defineCommand } from "citty";
import { createInterface } from "node:readline/promises";
import { stdin, stdout } from "node:process";
import { apiRequest } from "../lib/api";
import {
readSession,
removeSessions,
writeSession,
type KuberSession,
} from "../lib/session";
type LoginResponse = KuberSession;
async function promptUsername(): Promise<string> {
const readline = createInterface({ input: stdin, output: stdout });
try {
return (await readline.question("Username: ")).trim();
} finally {
readline.close();
}
}
async function promptPassword(): Promise<string> {
if (!stdin.isTTY || !stdin.setRawMode) {
throw new Error("Password input requires an interactive terminal");
}
stdout.write("Password: ");
stdin.setRawMode(true);
stdin.resume();
return new Promise((resolve, reject) => {
let password = "";
const cleanup = () => {
stdin.off("data", onData);
stdin.setRawMode(false);
stdin.pause();
stdout.write("\n");
};
const onData = (chunk: Buffer) => {
const value = chunk.toString("utf8");
if (value === "\u0003") {
cleanup();
reject(new Error("Login cancelled"));
return;
}
if (value === "\r" || value === "\n") {
cleanup();
resolve(password);
return;
}
if (value === "\u007f" || value === "\b") {
password = password.slice(0, -1);
return;
}
password += value;
};
stdin.on("data", onData);
});
}
export async function loginUser(
username: string,
password: string,
persistent: boolean,
): Promise<LoginResponse> {
const session = await apiRequest<LoginResponse>(
"/login",
{
method: "POST",
body: JSON.stringify({ username, password, persistent }),
},
{ authenticated: false },
);
await writeSession(session, persistent);
return session;
}
export const login = defineCommand({
meta: {
name: "login",
description: "Log in to kuber.astrxl.dev",
},
args: {
persist: {
type: "boolean",
description: "Keep the login across reboots",
},
},
async run({ args }) {
const username = String(args._[0] ?? "").trim() || (await promptUsername());
if (!username) throw new Error("Username is required");
const session = await loginUser(
username,
await promptPassword(),
Boolean(args.persist),
);
console.log(`Logged in as ${session.user.username}`);
},
});
export const logout = defineCommand({
meta: {
name: "logout",
description: "Log out of kuber.astrxl.dev",
},
async run() {
const session = await readSession();
if (session) {
try {
await apiRequest<void>(
"/logout",
{
method: "POST",
},
{ session },
);
} finally {
await removeSessions();
}
} else {
await removeSessions();
}
console.log("Logged out");
},
});
export const whoami = defineCommand({
meta: {
name: "whoami",
description: "Show the current kuber user",
},
async run() {
const result = await apiRequest<KuberSession["user"]>("/me");
console.log(`${result.username} (${result.roles.join(", ")})`);
},
});