Files

108 lines
4.9 KiB
JavaScript

// End-to-end auth + review flow: seed an admin, register a user, verify they
// cannot log in while pending, approve them, then verify login + /me.
import { buildServer } from '../lib/web/server.js'
import { resolveConfig } from '../lib/config.js'
import { hashPassword } from '../lib/web/auth.js'
function assert(condition, message) {
if (!condition) throw new Error('ASSERT: ' + message)
}
const app = await buildServer(resolveConfig({ port: 0, dbPath: ':memory:' }))
await app.listen({ port: 0 })
const base = `http://127.0.0.1:${app.server.address().port}`
// Seed the first admin directly (what `bootstrap-admin` does).
await app.db.createUser({
id: 'admin-1',
username: 'admin',
passHash: await hashPassword('adminpass123'),
role: 'admin',
homeDir: '/tmp/admin-home',
})
async function json(path, { method = 'GET', body, cookie } = {}) {
const res = await fetch(base + path, {
method,
headers: {
...(body ? { 'content-type': 'application/json' } : {}),
...(cookie ? { cookie } : {}),
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
return { status: res.status, body: text ? JSON.parse(text) : null, setCookie: res.headers.get('set-cookie') }
}
const sid = (setCookie) => (setCookie ? setCookie.split(';')[0] : undefined)
let r
r = await json('/api/auth/register', { method: 'POST', body: { username: 'alice', password: 'alicepass123' } })
console.log('register alice ->', r.status)
assert(r.status === 201, 'register creates a pending user')
r = await json('/api/auth/login', { method: 'POST', body: { username: 'alice', password: 'alicepass123' } })
console.log('login alice (pending) ->', r.status, r.body?.error)
assert(r.status === 403 && r.body?.error === 'pending_review', 'pending user is refused login')
r = await json('/api/auth/login', { method: 'POST', body: { username: 'admin', password: 'adminpass123' } })
console.log('login admin ->', r.status)
assert(r.status === 200, 'admin login succeeds')
const adminCookie = sid(r.setCookie)
r = await json('/api/admin/users', { cookie: adminCookie })
console.log('list users ->', r.status, r.body?.users?.map((u) => `${u.username}:${u.role}`))
assert(r.status === 200, 'admin can list users')
const alice = r.body.users.find((u) => u.username === 'alice')
assert(alice?.role === 'pending', 'alice listed as pending')
r = await json(`/api/admin/users/${alice.id}/approve`, { method: 'POST', cookie: adminCookie })
console.log('approve alice ->', r.status)
assert(r.status === 200, 'approve succeeds')
r = await json('/api/auth/login', { method: 'POST', body: { username: 'alice', password: 'alicepass123' } })
console.log('login alice (approved) ->', r.status, r.body?.user)
assert(r.status === 200 && r.body.user.role === 'active', 'approved user logs in')
const aliceCookie = sid(r.setCookie)
r = await json('/api/auth/me', { cookie: aliceCookie })
console.log('me (alice) ->', r.status, r.body?.user)
assert(r.status === 200 && r.body.user.username === 'alice', '/me returns the current user')
r = await json('/api/me/keys', { cookie: aliceCookie })
console.log('keys (none) ->', r.status, r.body)
assert(r.status === 200 && r.body.keys.length === 0, 'no keys initially')
r = await json('/api/me/keys', { method: 'POST', cookie: aliceCookie, body: { name: 'home', apiKey: 'sk-good1-abc' } })
console.log('keys (add home) ->', r.status, r.body?.key)
assert(r.status === 200 && r.body.key.enabled === true, 'first key added + enabled')
const firstKeyId = r.body.key.id
r = await json('/api/me/keys', { method: 'POST', cookie: aliceCookie, body: { name: 'server', apiKey: 'sk-good2-xyz' } })
console.log('keys (add server) ->', r.status, r.body?.key)
assert(r.status === 200 && r.body.key.enabled === true, 'second key added + enabled')
r = await json('/api/me/keys', { cookie: aliceCookie })
console.log('keys (list) ->', r.status, r.body?.keys?.map((k) => `${k.name}:${k.enabled}`))
assert(r.body.keys.length === 2 && r.body.keys.find((k) => k.name === 'server').enabled === true, 'two keys, server enabled')
r = await json(`/api/me/keys/${firstKeyId}/select`, { method: 'POST', cookie: aliceCookie })
assert(r.status === 200, 're-select first key')
r = await json('/api/me/keys', { cookie: aliceCookie })
assert(r.body.keys.find((k) => k.name === 'home').enabled === true, 'home enabled after select')
r = await json('/api/me/keys', { method: 'POST', cookie: aliceCookie, body: { name: 'bad', apiKey: 'bad key with space' } })
console.log('keys (bad charset) ->', r.status)
assert(r.status === 400, 'header-hostile key is rejected')
r = await json(`/api/me/keys/${firstKeyId}`, { method: 'DELETE', cookie: aliceCookie })
assert(r.status === 200, 'key deleted')
r = await json('/api/me/keys', { cookie: aliceCookie })
assert(r.body.keys.length === 1, 'one key left after delete')
await app.close()
console.log('OK: full auth + review + key vault flow passed')