Change how login works
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { PassThrough } from 'node:stream'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import test from 'brittle'
|
||||
|
||||
@@ -8,50 +9,80 @@ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
||||
|
||||
async function loadLoginBin() {
|
||||
const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8')
|
||||
const preamble = await readFile(
|
||||
path.join(__dirname, '../lib/interactive-masked-line.js'),
|
||||
'utf8'
|
||||
)
|
||||
const body = await readFile(path.join(__dirname, '../src/login.js'), 'utf8')
|
||||
return new AsyncFunction(
|
||||
'ctx',
|
||||
'argv',
|
||||
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
|
||||
`${runtime}\n${preamble}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
|
||||
)
|
||||
}
|
||||
|
||||
test('login prompts when passphrase arg missing', async (t) => {
|
||||
function mockTtyStreams() {
|
||||
const stdin = new PassThrough()
|
||||
stdin.isTTY = true
|
||||
stdin.setRawMode = () => {}
|
||||
const stdout = new PassThrough()
|
||||
return { stdin, stdout }
|
||||
}
|
||||
|
||||
test('login rejects passphrase on argv', async (t) => {
|
||||
const run = await loadLoginBin()
|
||||
let unlocked = ''
|
||||
const logs = []
|
||||
const errs = []
|
||||
const ctx = {
|
||||
console: { log: () => {}, error: (m) => logs.push(String(m)) },
|
||||
stdin: null,
|
||||
stdout: null,
|
||||
readLine: async () => 'secret-pass',
|
||||
console: {
|
||||
log: () => {},
|
||||
error: (m) => errs.push(String(m))
|
||||
},
|
||||
exitCode: 0,
|
||||
applyRegister: async () => {},
|
||||
applyUnlock: async (p) => {
|
||||
unlocked = p
|
||||
applyUnlock: async () => {
|
||||
logs.push('should-not')
|
||||
}
|
||||
}
|
||||
await run(ctx, ['login'])
|
||||
t.is(unlocked, 'secret-pass')
|
||||
await run(ctx, ['login', 'secret'])
|
||||
t.is(logs.length, 0)
|
||||
t.is(ctx.exitCode, 1)
|
||||
t.ok(errs.some((e) => e.includes('must not be given on the command line')))
|
||||
})
|
||||
|
||||
test('login requests masked shell prompt when readLine exists', async (t) => {
|
||||
test('login prompts via TTY streams (masked path)', async (t) => {
|
||||
const run = await loadLoginBin()
|
||||
/** @type {unknown} */
|
||||
let capturedOpts = null
|
||||
let unlocked = ''
|
||||
const { stdin, stdout } = mockTtyStreams()
|
||||
const ctx = {
|
||||
console: { log: () => {}, error: () => {} },
|
||||
readLine: async (_prompt, opts) => {
|
||||
capturedOpts = opts
|
||||
return 'secret-from-shell'
|
||||
},
|
||||
exitCode: 0,
|
||||
stdin,
|
||||
stdout,
|
||||
suspendReplForSubprocess() {},
|
||||
resumeReplAfterSubprocess() {},
|
||||
applyRegister: async () => {},
|
||||
applyUnlock: async (p) => {
|
||||
unlocked = p
|
||||
}
|
||||
}
|
||||
await run(ctx, ['login'])
|
||||
t.alike(capturedOpts, { mask: true })
|
||||
t.is(unlocked, 'secret-from-shell')
|
||||
const p = run(ctx, ['login'])
|
||||
await new Promise((r) => setImmediate(r))
|
||||
stdin.write('two words')
|
||||
stdin.write('\n')
|
||||
await p
|
||||
t.is(unlocked, 'two words')
|
||||
t.is(ctx.exitCode, 0)
|
||||
})
|
||||
|
||||
test('login --help', async (t) => {
|
||||
const run = await loadLoginBin()
|
||||
const logs = []
|
||||
const ctx = {
|
||||
console: { log: (m) => logs.push(String(m)), error: () => {} },
|
||||
exitCode: 1
|
||||
}
|
||||
await run(ctx, ['login', '--help'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.ok(logs.join('\n').includes('several words'))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user