Add the Hammerhead control plane

Dashboard and API, accounts (organizations, roles, invitations, 2FA, OIDC
single sign-on, API keys, revocable sessions), an encrypted SQLite database,
resource logins, alerts and /metrics, mail egress management with DKIM keys,
and the .env loader. Split out of the original single repository; depends on
Reef for the shared types. The end-to-end tests that run Hammerhead, Blacktip
and Bamboo together live in e2e/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-26 02:50:03 -06:00
1 parent 3daa368233
commit f521fa8668
35 files changed
+10392 -1

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign in</title>
<style>
:root { --bg:#0e1621; --panel:#16212f; --line:#25344a; --text:#e6edf5; --dim:#8da2bb; --accent:#06b6d4; --bad:#ef5350; }
@media (prefers-color-scheme: light) { :root { --bg:#f3f6fa; --panel:#fff; --line:#d5dde8; --text:#18222f; --dim:#5b6c80; --accent:#0891b2; } }
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--text); font:15px/1.45 system-ui,sans-serif; display:grid; place-items:center; min-height:100vh; padding:16px; }
.card { background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:24px; width:100%; max-width:380px; }
h1 { font-size:18px; margin:0 0 4px; }
.sub { color:var(--dim); margin:0 0 16px; font-size:13px; }
.sec { border-top:1px solid var(--line); padding-top:14px; margin-top:14px; display:flex; flex-direction:column; gap:8px; }
.sec:first-of-type { border-top:0; padding-top:0; margin-top:0; }
h2 { font-size:12px; text-transform:uppercase; letter-spacing:.6px; color:var(--dim); margin:0; }
input { background:var(--bg); color:var(--text); border:1px solid var(--line); border-radius:6px; padding:9px 10px; font:inherit; width:100%; }
input:focus { outline:2px solid var(--accent); outline-offset:-1px; }
button, a.btn { background:var(--accent); color:#fff; border:0; border-radius:6px; padding:9px 12px; font:inherit; font-weight:600; cursor:pointer; text-align:center; text-decoration:none; display:block; }
button.ghost, a.ghost { background:none; border:1px solid var(--line); color:var(--text); }
#err { color:var(--bad); min-height:18px; font-size:13px; margin-top:10px; }
.ok { color:var(--dim); font-size:13px; }
[hidden] { display:none !important; }
</style>
</head>
<body>
<div class="card">
<h1 id="title">Sign in</h1>
<p class="sub" id="sub"></p>
<div id="body"></div>
<div id="err"></div>
</div>
<script>
const id = location.pathname.split('/')[3];
const params = new URLSearchParams(location.search);
const ret = params.get('return') || '';
const $ = s => document.querySelector(s);
const body = $('#body');
function h(tag, props, ...kids) {
const el = document.createElement(tag);
for (const [k, v] of Object.entries(props || {})) {
if (k.startsWith('on')) el.addEventListener(k.slice(2), v);
else if (v === true) el.setAttribute(k, '');
else if (v != null && v !== false) el.setAttribute(k, v);
}
for (const k of kids.flat()) if (k != null) el.append(k.nodeType ? k : document.createTextNode(k));
return el;
}
const err = m => $('#err').textContent = m || '';
async function post(path, data) {
err('');
const r = await fetch(path, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
const j = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(j.error || r.statusText);
return j;
}
async function finish(path, data) {
try {
const j = await post(`/auth/r/${id}/${path}`, { ...data, return: ret });
if (j.redirect) location.href = j.redirect;
} catch (e) { err(e.message); }
}
function section(title, ...kids) { return h('div', {class:'sec'}, h('h2', {}, title), ...kids); }
function passwordForm(kind, label, type) {
const i = h('input', {type, placeholder:label, autocomplete: kind === 'password' ? 'current-password' : 'off', inputmode: kind === 'pin' ? 'numeric' : null});
const go = () => finish(kind, { [kind]: i.value });
i.addEventListener('keydown', e => e.key === 'Enter' && go());
return section(label, i, h('button', {onclick:go}, 'Continue'));
}
function emailForm() {
const email = h('input', {type:'email', placeholder:'you@example.com', autocomplete:'email'});
const code = h('input', {placeholder:'6-digit code', inputmode:'numeric', hidden:true});
const msg = h('div', {class:'ok'});
const go = h('button', {}, 'Email me a code');
go.onclick = async () => {
try {
if (code.hidden) {
const j = await post(`/auth/r/${id}/email/start`, { email: email.value, return: ret });
msg.textContent = j.status; code.hidden = false; go.textContent = 'Verify code'; code.focus();
} else await finish('email/verify', { email: email.value, code: code.value });
} catch (e) { err(e.message); }
};
return section('Email code', email, code, msg, go);
}
function ssoForm(info) {
const kids = [];
if (info.user) {
if (info.user.allowed) {
kids.push(h('div', {class:'ok'}, `Signed in as ${info.user.email}`), h('button', {onclick:() => finish('sso', {})}, 'Continue'));
} else {
kids.push(h('div', {class:'ok'}, `${info.user.email} doesn't have access to this resource.`));
}
kids.push(h('button', {class:'ghost', onclick: async () => { await fetch('/api/logout', {method:'POST'}); location.reload(); }}, 'Use a different account'));
return section('Account', ...kids);
}
const email = h('input', {type:'email', placeholder:'Email', autocomplete:'username'});
const pw = h('input', {type:'password', placeholder:'Password', autocomplete:'current-password'});
const code = h('input', {placeholder:'Authenticator code', inputmode:'numeric', autocomplete:'one-time-code', hidden:true});
let ticket = null;
const go = async () => {
try {
let j;
if (ticket) j = await post('/api/login/totp', { ticket, code: code.value });
else j = await post('/api/login', { email: email.value, password: pw.value });
if (j.needTotp) { ticket = j.ticket; code.hidden = false; email.hidden = pw.hidden = true; code.focus(); return; }
location.reload();
} catch (e) { err(e.message); }
};
[email, pw, code].forEach(i => i.addEventListener('keydown', e => e.key === 'Enter' && go()));
kids.push(email, pw, code, h('button', {onclick:go}, 'Sign in'));
for (const p of info.providers || []) {
const back = `/auth/r/${id}?return=${encodeURIComponent(ret)}`;
kids.push(h('a', {class:'btn ghost', href:`/auth/oidc/${p.id}/start?return=${encodeURIComponent(back)}`}, `Continue with ${p.name}`));
}
return section('Account', ...kids);
}
async function main() {
// share links skip the form
const share = params.get('share');
if (share) { $('#title').textContent = 'Checking link…'; return finish('share', { token: share }); }
try {
const r = await fetch(`/auth/r/${id}/info?return=${encodeURIComponent(ret)}`);
const info = await r.json();
if (!r.ok) throw new Error(info.error);
$('#title').textContent = info.name;
$('#sub').textContent = `${info.host} is protected. Sign in to continue.`;
if (info.sso) body.append(ssoForm(info));
if (info.password) body.append(passwordForm('password', 'Password', 'password'));
if (info.pin) body.append(passwordForm('pin', 'PIN', 'password'));
if (info.email) body.append(emailForm());
if (!body.children.length) err('No sign-in method is available.');
} catch (e) { $('#title').textContent = 'Unavailable'; err(e.message); }
}
main();
</script>
</body>
</html>