D2 — Forge user lifecycle (REST client + create/check/token-gen flow) #4

Open
opened 2026-05-20 21:42:09 +00:00 by mik-tf · 0 comments
Owner

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:

  1. Add a Forge REST client to hero_os_tfgrid_deployer_server. Probably crates/hero_os_tfgrid_deployer_server/src/forge/mod.rs with a single ForgeClient struct holding the base URL + admin token.
  2. Method: ForgeClient::user_exists(username) -> Result<Option<User>> — GET /api/v1/users/<username> — returns the user if present, None on 404, error on other failures.
  3. Method: ForgeClient::create_user(username, display_name, email) -> Result<User> — POST /api/v1/admin/users with a generated random password (alphanumeric, 32 chars). Requires admin scope on the deployer's Forge token.
  4. Method: 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 keyed deployer/users/<user_id>/forge_token.
  5. Store password OOB — random password generated in step 3 is returned to the admin operator (deployer admin UI shows it once + a "copy" button + a "regenerate" action). NEVER stored persistently in the deployer.

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) -> User
  • deployer.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 _admin crate, add /users page:

  • Table of users (forge username, display name, # of VMs, created at, last activity)
  • "Create user" form → calls deployer.create_user
  • After creation, modal shows the initial password + "copy to clipboard" + note "share this OOB; deployer doesn't store it persistently"
  • Per-user action: "Generate new Forge token" (rotates the stored token)

Auth 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

  • Q1: Does forge.ourworld.tf's POST /api/v1/admin/users allow a service token to set the initial password directly, or does it auto-generate + email the user? We want admin-set, share-OOB.
  • Q2: Does Forge support creating access tokens on behalf of another user (with admin scope), or do we need to instruct the user to create their own token first? Affects step 4 — if not, the BYO-key-flow in cockpit becomes mandatory for FORGE_TOKEN.
  • Q3: Email field — Forge requires this? If yes, we use a placeholder for demo accounts (e.g. <username>@nomail.demo.ourworld.tf) and let users update it later via cockpit's settings page.

These map to /forge_api skill notes; will check there first, then escalate to admin if unanswered.

Acceptance criteria

  • deployer.create_user end-to-end against forge.ourworld.tf — checks if exists, creates if not, generates Forge token, stores in hero_proc secret
  • Idempotent: re-running with same username returns the existing user without erroring
  • Admin UI form + result modal work
  • Deployer's Forge admin token never appears in logs, sqlite, or git
  • Tests: integration test against forge.ourworld.tf in a CI-controlled namespace (need a deployer_test_* username convention so we can re-run without polluting Forge)

References

## D2 — Forge user lifecycle (REST client + create/check/token-gen flow) Sub-issue of [`#?` (v0.1 scope)](https://forge.ourworld.tf/lhumina_code/hero_os_tfgrid_deployer/issues/2). Wires the deployer to Forge as the identity authority. ## What this does Per the [meeting notes](https://forge.ourworld.tf/lhumina_code/hero_os_tfgrid_deployer/issues/1): "go on forge, over rest — check that user exists, if user does not exist, create the user — random password, generate a forge key". Implementation: 1. **Add a Forge REST client** to `hero_os_tfgrid_deployer_server`. Probably `crates/hero_os_tfgrid_deployer_server/src/forge/mod.rs` with a single `ForgeClient` struct holding the base URL + admin token. 2. **Method: `ForgeClient::user_exists(username) -> Result<Option<User>>`** — GET `/api/v1/users/<username>` — returns the user if present, None on 404, error on other failures. 3. **Method: `ForgeClient::create_user(username, display_name, email) -> Result<User>`** — POST `/api/v1/admin/users` with a generated random password (alphanumeric, 32 chars). Requires admin scope on the deployer's Forge token. 4. **Method: `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 keyed `deployer/users/<user_id>/forge_token`. 5. **Store password OOB** — random password generated in step 3 is returned to the admin operator (deployer admin UI shows it once + a "copy" button + a "regenerate" action). NEVER stored persistently in the deployer. ## 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) -> User` - `deployer.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 `_admin` crate, add `/users` page: - Table of users (forge username, display name, # of VMs, created at, last activity) - "Create user" form → calls `deployer.create_user` - After creation, modal shows the initial password + "copy to clipboard" + note "share this OOB; deployer doesn't store it persistently" - Per-user action: "Generate new Forge token" (rotates the stored token) ## Auth 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 - **Q1:** Does forge.ourworld.tf's `POST /api/v1/admin/users` allow a service token to set the initial password directly, or does it auto-generate + email the user? We want admin-set, share-OOB. - **Q2:** Does Forge support creating access tokens on behalf of another user (with admin scope), or do we need to instruct the user to create their own token first? Affects step 4 — if not, the BYO-key-flow in cockpit becomes mandatory for FORGE_TOKEN. - **Q3:** Email field — Forge requires this? If yes, we use a placeholder for demo accounts (e.g. `<username>@nomail.demo.ourworld.tf`) and let users update it later via cockpit's settings page. These map to `/forge_api` skill notes; will check there first, then escalate to admin if unanswered. ## Acceptance criteria - `deployer.create_user` end-to-end against forge.ourworld.tf — checks if exists, creates if not, generates Forge token, stores in hero_proc secret - Idempotent: re-running with same username returns the existing user without erroring - Admin UI form + result modal work - Deployer's Forge admin token never appears in logs, sqlite, or git - Tests: integration test against forge.ourworld.tf in a CI-controlled namespace (need a `deployer_test_*` username convention so we can re-run without polluting Forge) ## References - Meeting notes: [`#1`](https://forge.ourworld.tf/lhumina_code/hero_os_tfgrid_deployer/issues/1) - Skill: `/forge_api` - Umbrella: [`#?` (v0.1 scope)](https://forge.ourworld.tf/lhumina_code/hero_os_tfgrid_deployer/issues/2)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_os_tfgrid_deployer#4
No description provided.