feat(auth): center-scoped single admin + GET /admin-email + cli
Triage channels in Guilds need to deliver every message to a single
"observer" admin user across the whole Center deployment (regardless of
on-duty / mention). This adds the data + auth surface for that.
## Schema
`users.isAdmin TINYINT DEFAULT 0` (synchronize:true auto-applies; cli
`user set-admin` enforces at-most-one in a transaction).
## CLI (subject `user`)
- `set-admin --email <e>` — clears every other admin row first, then
marks the target. Returns `{admin: {email, userId}}`
- `clear-admin` — unsets all (returns `{cleared: N}`)
- `show-admin` — prints `{admin: {email, userId}}` or `{admin: null}`
## HTTP
`GET /auth/admin-email` (NOT @Public — requires a guild-node api key
via the existing CenterApiKeyGuard). Returns:
- `{email: "...", userId: "..."}` if an admin exists
- `null` (literal JSON) if no admin
Guild backends cache the result (1 day TTL per spec; with cli refresh
override on guild side).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
src/cli.ts
27
src/cli.ts
@@ -15,6 +15,9 @@ function printUsageAndExit(): never {
|
||||
console.error('Usage:');
|
||||
console.error(' node dist/cli.js user create --email <email> --password <password>');
|
||||
console.error(' node dist/cli.js user apikey --email <email> [--label <label>]');
|
||||
console.error(' node dist/cli.js user set-admin --email <email>');
|
||||
console.error(' node dist/cli.js user clear-admin');
|
||||
console.error(' node dist/cli.js user show-admin');
|
||||
console.error(' node dist/cli.js node register --node-id <id> --name <name> --endpoint <url>');
|
||||
console.error(
|
||||
' node dist/cli.js config oidc [--issuer <url>] [--client-id <id>] [--client-secret <s>]\n' +
|
||||
@@ -52,6 +55,30 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === 'user' && action === 'set-admin') {
|
||||
const email = getArg('--email');
|
||||
if (!email) printUsageAndExit();
|
||||
|
||||
const auth = app.get(AuthService);
|
||||
const res = await auth.setAdmin(email);
|
||||
process.stdout.write(JSON.stringify({ ok: true, admin: res }) + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === 'user' && action === 'clear-admin') {
|
||||
const auth = app.get(AuthService);
|
||||
const res = await auth.clearAdmin();
|
||||
process.stdout.write(JSON.stringify({ ok: true, ...res }) + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === 'user' && action === 'show-admin') {
|
||||
const auth = app.get(AuthService);
|
||||
const res = await auth.getAdminEmail();
|
||||
process.stdout.write(JSON.stringify({ ok: true, admin: res }) + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === 'node' && action === 'register') {
|
||||
const nodeId = getArg('--node-id');
|
||||
const name = getArg('--name');
|
||||
|
||||
Reference in New Issue
Block a user