D2 — Forge user lifecycle (REST client + create/check/token-gen flow) #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
D2 — Forge user lifecycle (REST client + create/check/token-gen flow)
Sub-issue of
#?(v0.1 scope). Wires the deployer to Forge as the identity authority.What this does
Per the meeting notes: "go on forge, over rest — check that user exists, if user does not exist, create the user — random password, generate a forge key".
Implementation:
hero_os_tfgrid_deployer_server. Probablycrates/hero_os_tfgrid_deployer_server/src/forge/mod.rswith a singleForgeClientstruct holding the base URL + admin token.ForgeClient::user_exists(username) -> Result<Option<User>>— GET/api/v1/users/<username>— returns the user if present, None on 404, error on other failures.ForgeClient::create_user(username, display_name, email) -> Result<User>— POST/api/v1/admin/userswith a generated random password (alphanumeric, 32 chars). Requires admin scope on the deployer's Forge token.ForgeClient::generate_token_for(user, scopes) -> Result<TokenString>— Forge's "create access token" admin endpoint, on behalf of the new user. Token captured + stored as a hero_proc secret keyeddeployer/users/<user_id>/forge_token.OpenRPC additions to deployer
deployer.create_user(username, display_name?, email?) -> { user_id, forge_username, initial_password, forge_token_set: bool }deployer.get_user(user_id) -> Userdeployer.list_users() -> [User]deployer.delete_user(user_id)— removes from deployer sqlite; does NOT delete from Forge (admin operator handles Forge cleanup if wanted)Admin UI
In
_admincrate, add/userspage:deployer.create_userAuth model
The deployer's Forge admin token is itself a hero_proc secret:
deployer/forge_admin_token. Set once during deployer install. NEVER in code, env vars, or sqlite.Open questions for Forge / admin team
POST /api/v1/admin/usersallow a service token to set the initial password directly, or does it auto-generate + email the user? We want admin-set, share-OOB.<username>@nomail.demo.ourworld.tf) and let users update it later via cockpit's settings page.These map to
/forge_apiskill notes; will check there first, then escalate to admin if unanswered.Acceptance criteria
deployer.create_userend-to-end against forge.ourworld.tf — checks if exists, creates if not, generates Forge token, stores in hero_proc secretdeployer_test_*username convention so we can re-run without polluting Forge)References
#1/forge_api#?(v0.1 scope)mik-tf referenced this issue2026-05-21 21:59:37 +00:00
s140 (2026-05-21) — landing: BYO via cockpit Settings (Bundle B);
generate_token_fordropped from D2 scopes140 (Track A → Track D pivot) ran Phase B.5 + a live API probe to resolve the open hedge in #2 step 1 ("may need to ask admin to generate one, or use the deployer's admin token to mint one if Forge allows it"). Outcome: deployer never mints or stores a token on behalf of the end user.
Probe result (verbatim)
Authorization: token <admin>POST /api/v1/users/<u>/tokensAuthorization: token <admin>+Sudo: <u>headerPOST /api/v1/users/<u>/tokens<u>:<initial_password>POST /api/v1/users/<u>/tokenssha1Forgejo refuses bearer-token auth on the user-token endpoint regardless of admin scope or
Sudo:header. Only HTTP basic auth as the user works. Basic-auth bridge would be technically viable (deployer holds the password it just set), but is rejected on security grounds — see locked decision D-22 for the full rationale (deployer would become a long-lived impersonation-credential vault, repudiation breaks, deployer-minted token outlives password change, conflates one-shot bootstrap with long-lived bearer).Decision (D-22, locked)
POST /api/v1/admin/userswithAuthorization: token <deployer_admin_token>(token at hero_proc secret slotdeployer/FORGE_TOKEN, nevercore/FORGE_TOKENper D-16 precedent)."must_change_password": true— production hardening; first Forge login forces password change, neutering OOB password disclosure within minutes.generate_token_forcall. That surface is dropped from D2 scope.CreateUserOutput { user_id, forge_username, initial_password }—forge_tokenfield removed.write:issuefor v1 feedback). Already on forge.ourworld.tf from the OAuth-gated VM front-door login per #2 goal — ~30s detour in the same session.cockpit/USER_FORGE_TOKENper D-16 (already shipped at hero_cockpit s135).deployer/users/<id>/forge_tokenis not created in D2 (reserved-but-unused; available if a future-Bundle-A pivot is ever made).Spec hedge resolved
The four canonical sources together point at BYO once the hedge in #2 step 1 is read alongside:
Code shape for D2 (revised from the original "create/check/token-gen flow" framing)
crates/hero_tfgrid_deployer_server/src/forge.rs(new module):herolib_tools::forge::ForgeClientconstructed via a new upstreamconnect_with_secret(context: &str, key: &str)constructor (lands as a separate squash onhero_libbefore D2 code — see s141 plan).user_exists(username) -> Result<Option<AccountInfo>>viaGET /api/v1/users/<username>(404 → None).create_user(username, display_name, email) -> Result<CreateUserOutput>viaPOST /api/v1/admin/userswith random 32-char alphanumeric password +must_change_password=true. Idempotent:user_existscheck before POST; on 409 return existing.OpenRPC additions:
deployer.create_user,deployer.get_user,deployer.list_users. (Skipdelete_userfor D2 — per #2 decommission flow the Forge user is never deleted.)Followup: Option 3 (OAuth-session-reuse) filed on
hero_cockpitA polish path was identified: if hero_proxy can be made to pass through the OAuth access token (used to gate the VM front door) to backend cockpit calls, cockpit could transparently reuse that session for Forge API calls without ever asking the user to paste a token. Filed as a separate hero_cockpit issue at s140 close-out. Forward-compatible — the
cockpit/USER_FORGE_TOKENslot stays valid as fallback. Not in D2 scope.References
decisions/D-22-deployer-forge-token-namespacing-and-byo-landing.md)memory/investigation_forge_admin_token_mint_2026_05_21.mdsessions/140.yml(s140 close-out)Signed-by: mik-tf mik-tf@noreply.invalid