88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import { createInterface } from 'node:readline';
|
|
import { signIn, saveSession } from '../../auth.js';
|
|
import { readMeta } from './daemon.js';
|
|
|
|
/**
|
|
* Prompt for a single line of input (with echo).
|
|
*/
|
|
export function promptLine(question: string): Promise<string> {
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
return new Promise((resolve) => {
|
|
rl.question(question, (answer) => {
|
|
rl.close();
|
|
resolve(answer.trim());
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Prompt for a secret value without echoing the typed characters to the terminal.
|
|
* Uses TTY raw mode when available so that passwords do not appear in terminal
|
|
* recordings, scrollback, or shared screen sessions.
|
|
*/
|
|
export function promptSecret(question: string): Promise<string> {
|
|
return new Promise((resolve) => {
|
|
process.stdout.write(question);
|
|
if (process.stdin.isTTY) {
|
|
process.stdin.setRawMode(true);
|
|
}
|
|
process.stdin.resume();
|
|
process.stdin.setEncoding('utf-8');
|
|
|
|
let secret = '';
|
|
const onData = (char: string): void => {
|
|
if (char === '\n' || char === '\r' || char === '\u0004') {
|
|
process.stdout.write('\n');
|
|
if (process.stdin.isTTY) {
|
|
process.stdin.setRawMode(false);
|
|
}
|
|
process.stdin.pause();
|
|
process.stdin.removeListener('data', onData);
|
|
resolve(secret);
|
|
} else if (char === '\u0003') {
|
|
// ^C
|
|
process.stdout.write('\n');
|
|
if (process.stdin.isTTY) {
|
|
process.stdin.setRawMode(false);
|
|
}
|
|
process.stdin.pause();
|
|
process.stdin.removeListener('data', onData);
|
|
process.exit(130);
|
|
} else if (char === '\u007f' || char === '\b') {
|
|
secret = secret.slice(0, -1);
|
|
} else {
|
|
secret += char;
|
|
}
|
|
};
|
|
process.stdin.on('data', onData);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Shared login helper used by both `mosaic login` and `mosaic gateway login`.
|
|
* Prompts for email/password if not supplied, signs in, and persists the session.
|
|
*/
|
|
export async function runLogin(opts: {
|
|
gatewayUrl: string;
|
|
email?: string;
|
|
password?: string;
|
|
}): Promise<void> {
|
|
const email = opts.email ?? (await promptLine('Email: '));
|
|
// Do not trim password — it may intentionally contain leading/trailing whitespace
|
|
const password = opts.password ?? (await promptSecret('Password: '));
|
|
|
|
const auth = await signIn(opts.gatewayUrl, email, password);
|
|
saveSession(opts.gatewayUrl, auth);
|
|
console.log(`Signed in as ${auth.email} (${opts.gatewayUrl})`);
|
|
}
|
|
|
|
/**
|
|
* Derive the gateway base URL from meta.json with a fallback.
|
|
*/
|
|
export function getGatewayUrl(overrideUrl?: string): string {
|
|
if (overrideUrl) return overrideUrl;
|
|
const meta = readMeta();
|
|
if (meta) return `http://${meta.host}:${meta.port.toString()}`;
|
|
return 'http://localhost:14242';
|
|
}
|