Commit Graph

22 Commits

Author SHA1 Message Date
h z
cea3842c50 Merge pull request 'feat(auth): center-scoped single admin + GET /admin-email + cli' (#1) from feat/admin-user into main 2026-05-22 21:59:14 +00:00
hanghang zhang
bd53813391 fix(cli): set-admin transaction uses isAdmin=true filter (TypeORM rejects empty-where)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:25:06 +01:00
hanghang zhang
06756af8bc 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>
2026-05-22 22:09:58 +01:00
9f7216565b fix(center): graceful 4xx when OIDC issuer/token endpoint unreachable
Wrap discovery + token-exchange fetch in try/catch so a DNS/network
failure returns BadRequest/Unauthorized instead of a bare 500.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:47:28 +01:00
2a394969d2 feat(center): OIDC login (auto-provision by email) + CLI config-oidc
- OidcConfig entity (single row) + DB registration.
- OidcService: discovery, Authorization Code + PKCE, state/ticket as
  short-lived signed JWTs (no server session), userinfo/id_token claim
  resolution. Auto-provisions a passwordless user by email.
- Public endpoints /auth/oidc/{status,start,callback,exchange}; callback
  bounces a one-time ticket to the SPA in the URL fragment.
- AuthService.provisionOidcUser + issueSessionForUserId (login shape).
- CLI: 'config oidc' upserts issuer/clientId/secret/callback/redirect/
  scopes/enabled (secret masked on print).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:44:49 +01:00
6afb935302 fix(security): close Critical auth gaps (C1/C2/C3)
C1: replace fragile path.endsWith() guard whitelist with a metadata
    @Public() decorator + Reflector (no more path-shape bypass surface).
C2: CenterApiKeyGuard attaches the authenticated GuildNode; introspect
    & resolve-names now reject when body.guildNodeId != that node
    (stops one node probing/enumerating another guild's identities).
C3: heartbeat/status are self-only (a node can't revoke/hijack another);
    GET /nodes no longer returns apiKeyHash (credential-hash leak).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:47:01 +01:00
aa9d59a952 docs: rewrite README to match current architecture
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:53:22 +01:00
44aa34d1ff refactor: migrate to ES modules
package.json type=module, tsconfig module/moduleResolution=NodeNext,
target es2022, explicit .js on all relative imports. Center: jsonwebtoken
& bcryptjs switched to default imports (ESM/CJS interop). Verified:
builds, boots, full auth + plugin round-trip work under ESM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:47:35 +01:00
dea946653b feat(center): API-key agent auth
UserApiKey (apiKeyHash->userId); CLI 'user apikey --email'; POST
/auth/agent/login {apiKey} -> normal user session (api-key-guard exempt).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:52:42 +01:00
3da51a60bc feat(center): POST /auth/resolve-names
Resolve display names/emails to userIds within a guild node's active
members (api-key auth). Used by guild for <@user.name:NAME> translation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:47:01 +01:00
bc0d1ba8bf feat(center): user display name + GET/PATCH /auth/me
- User.name column, defaults to email on register
- GET /auth/me, PATCH /auth/me to view/change own name (api-key exempt)
- login + guild members responses now include name

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:09:40 +01:00
nav
2792f78ada fix(center): exempt guild join/members auth endpoints from api-key guard 2026-05-15 00:32:33 +00:00
nav
1eb30348a2 feat(center): guild join and guild members APIs; stop auto-joining all guilds 2026-05-14 16:57:57 +00:00
nav
ebc3571823 fix(center): enable CORS for auth preflight and desktop origins 2026-05-14 16:26:55 +00:00
nav
0b32dc8e3c feat(cli): move user and guild registration from API to local CLI 2026-05-14 14:43:59 +00:00
nav
7afd220b4a feat(auth): split api-key boundary for frontend auth flow 2026-05-14 14:17:07 +00:00
nav
81dfc227e3 refactor(center): prefix environment variables with FABRIC_BACKEND_CENTER 2026-05-13 12:58:28 +00:00
nav
0a4cb62065 refactor(center): local-only guild register endpoint without shared secret 2026-05-13 08:41:45 +00:00
nav
1c07f43032 refactor(center): introspect relies on api key auth instead of shared secret 2026-05-13 08:36:06 +00:00
nav
cfa5ccdfaf feat(center): enforce API key on all APIs except node register 2026-05-13 08:17:42 +00:00
root
a924bf656d feat(center): issue per-guild tokens and add introspection API 2026-05-13 07:59:27 +00:00
nav
03a3342d2a feat: bootstrap from Fabric monorepo 2026-05-13 07:06:02 +00:00