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:
@@ -84,6 +84,30 @@ type AddUserReq struct {
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// CreateInviteReq is the create-invite payload from the SPA. The admin fixes the
|
||||
// handle and role the future user will receive; TTLSecs is optional (0 uses the
|
||||
// bus default of 7 days). The admin never supplies a key — the user's client
|
||||
// generates its own keypair and publishes only its public keys at /register.
|
||||
type CreateInviteReq struct {
|
||||
Handle string `json:"handle"`
|
||||
Role string `json:"role"`
|
||||
TTLSecs int `json:"ttl_secs"`
|
||||
}
|
||||
|
||||
// InviteView is a single-use registration invite as the admin panel sees it. The
|
||||
// token is the bearer secret the admin turns into a join link; JoinURL is that
|
||||
// link, pre-built by the gateway from the configured client base URL so the SPA
|
||||
// does not have to know where the client lives.
|
||||
type InviteView struct {
|
||||
Token string `json:"token"`
|
||||
Handle string `json:"handle"`
|
||||
Role string `json:"role"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
Used bool `json:"used"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
JoinURL string `json:"join_url"`
|
||||
}
|
||||
|
||||
// MeInfo describes the gateway's own identity and which capabilities are wired,
|
||||
// so the SPA can render the operator endpoint and label the Users tab's backend.
|
||||
type MeInfo struct {
|
||||
@@ -91,6 +115,12 @@ type MeInfo struct {
|
||||
SignPub string `json:"sign_pub"`
|
||||
UsersBackend string `json:"users_backend"` // "control-plane" (signed HTTP) | "sqlite" (single-node fallback)
|
||||
Mock bool `json:"mock"`
|
||||
// JoinBaseURL is the base URL of the END-USER client (the page that hosts
|
||||
// /join?token=…), configured on the gateway (--join-base-url / env
|
||||
// UNIBUS_JOIN_BASE_URL). It is NOT the admin panel's own URL: the join link
|
||||
// the admin shares points at the user-facing client, a separate app. Empty
|
||||
// when unconfigured; the SPA then falls back to its own origin and warns.
|
||||
JoinBaseURL string `json:"join_base_url"`
|
||||
}
|
||||
|
||||
// Repo is the data source behind the REST API. Two implementations exist:
|
||||
@@ -121,4 +151,14 @@ type Repo interface {
|
||||
ListUsers(ctx context.Context) ([]UserView, error)
|
||||
AddUser(ctx context.Context, req AddUserReq) error
|
||||
RevokeUser(ctx context.Context, signPub string) error
|
||||
// DeleteUser hard-deletes a user (purge), distinct from RevokeUser's status
|
||||
// flip. The admin panel maps its "Eliminar (permanente)" action here.
|
||||
DeleteUser(ctx context.Context, signPub string) error
|
||||
|
||||
// Invites (the wallet-model account-creation path). CreateInvite mints a
|
||||
// single-use registration link the admin shares; the user redeems it from
|
||||
// their own client without the admin ever handling a private key. ListInvites
|
||||
// returns the pending links.
|
||||
CreateInvite(ctx context.Context, req CreateInviteReq) (InviteView, error)
|
||||
ListInvites(ctx context.Context) ([]InviteView, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user