feat(gateway): invite and hard-delete REST endpoints + repo methods
Wire the bus's new account surface into the admin gateway:
- POST /api/invites, GET /api/invites: mint and list single-use registration
invites (CreateInvite/ListInvites on the Repo). The gateway pre-builds the
shareable join link (JoinURL) from a configurable end-user client base URL so
the SPA does not need to know where the client lives.
- DELETE /api/users/{pub}: hard-delete (purge) a user, distinct from the existing
revoke.
- Both backends covered: signed control-plane (cluster default) via the unibus
client's CreateInvite/ListInvites/DeleteUser, and the direct membership store
(single-node --db fallback). For the direct store, ListInvites filters to
pending (the control plane already does so server-side).
- New --join-base-url flag / UNIBUS_JOIN_BASE_URL env feeds the join link base
URL (the END-USER client, NOT the panel's own URL); surfaced on /api/me.
- Mock repo gains the same methods for UI iteration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,10 +37,20 @@ func main() {
|
||||
identityFile = flag.String("identity-file", "", "path to the admin identity JSON file (0600). Mutually exclusive with --identity-pass")
|
||||
identityPass = flag.String("identity-pass", "", "pass(1) entry holding the admin identity JSON, e.g. unibus/operator-identity")
|
||||
dbPath = flag.String("db", "", "OPTIONAL membership SQLite path for single-node user management. Empty (default) = manage users via the signed control-plane API, which works in cluster")
|
||||
joinBaseURL = flag.String("join-base-url", "", "base URL of the END-USER client that hosts /join?token=… (e.g. https://chat.unibus.example). Used to build shareable invite links. Falls back to env UNIBUS_JOIN_BASE_URL")
|
||||
mock = flag.Bool("mock", false, "serve sample data instead of talking to the bus (UI iteration)")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
// The end-user client base URL (for invite join links) comes from the flag or,
|
||||
// if unset, the env var. It is NOT the admin panel's own URL — the join link
|
||||
// points at the user-facing client, a separate app. Empty leaves the SPA to
|
||||
// fall back to its own origin and warn.
|
||||
joinBase := *joinBaseURL
|
||||
if joinBase == "" {
|
||||
joinBase = os.Getenv("UNIBUS_JOIN_BASE_URL")
|
||||
}
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lmsgprefix)
|
||||
log.SetPrefix("[unibus_admin] ")
|
||||
|
||||
@@ -83,6 +93,7 @@ func main() {
|
||||
Nodes: nodes,
|
||||
Store: store,
|
||||
StoreBackend: backend,
|
||||
JoinBaseURL: joinBase,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
@@ -98,6 +109,11 @@ func main() {
|
||||
tls = "ON (CA " + *caPath + ")"
|
||||
}
|
||||
log.Printf("bus TLS+nkey: %s", tls)
|
||||
if joinBase != "" {
|
||||
log.Printf("invite join base: %s", joinBase)
|
||||
} else {
|
||||
log.Printf("invite join base: (unset; SPA falls back to its own origin — set --join-base-url or UNIBUS_JOIN_BASE_URL)")
|
||||
}
|
||||
}
|
||||
|
||||
srv := admin.NewServer(repo, files)
|
||||
|
||||
Reference in New Issue
Block a user