Phase 9 — Forge OAuth login + dual-auth (D-18) #10
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?
Parent: hero_onboarding#1 (paid-tier overlay milestone)
Decision lock: decisions/D-18-dual-auth-model.md
Status: ✅ implemented + smoke-green; squash-merge pending boss OK.
Scope
Add Forge OAuth as the default login mechanism on hero_onboarding, alongside the D-12 mycelium proof-of-control flow. Per-User row carries two optional identity slots (
forge_id?+mycelium_address?); at least one populated. Snapshotforge_idontoVmAllocationrows at allocate time so cockpit's future Forge-OAuth SSO bridge can gate "is this VM yours?" without an OSIS lookup.What landed
User.mycelium_addressbecomes optional;User.forge_id?gets@indexannotation.VmAllocationgainsforge_id?: str. Docstring rewritten for dual-auth.crates/hero_onboarding_server/src/forge_oauth.rs(~360 LOC + 11 unit tests). Forgejo OAuth2 v1 wire: PKCE-S256 + HMAC-SHA256-signed state token (5-min TTL, bundles PKCE verifier — cookie-free). Userinfo via server-sideGET /api/v1/user. Access tokens single-use, never persisted.forge_oauth: Option<Arc<ForgeOAuthConfig>>. Registration-aware: server starts cleanly if CLIENT_ID/SECRET missing; routes return 503; login page hides Forge button.GET /login/forge/start,GET /login/forge/callback,GET /account,POST /account/link-forge,POST /account/unlink-forge(defensive 409 if it would orphan the user row).user.sid(wasmycelium_address). All cookie-read sites go throughresolve_user_from_cookie(osis, value)which accepts three formats: bare sid (canonical), mycelium address with auto-create (smoke compat),fid:<forge_id>prefix (smoke convenience).vm_allocate_postreadsuser.forge_idand writes onto the new allocation row post-provisioner.allocate(). Snapshot, not foreign-key.forge_idcolumn.Q lock summary
http://127.0.0.1:9920/login/forge/callback. Prod redirect at s2-012.read:user.Acceptance
cargo testworkspace: 62/62 (53 server + 9 schema; +11 new forge_oauth unit tests).lab build --release --install --workspaceVICTORY 3/3 (24.6s build #12).lab infocheck3/3 clean / 0 findings.cargo fmt --check+cargo clippy --workspace --all-targets -- -D warningsclean.scripts/smoke_forge_oauth.sh29/29 GREEN (new — Python mock Forge OAuth server).scripts/smoke_dual_auth.sh15/15 GREEN (new — end-to-end dual-identity + allocation snapshot).Totals: 62 unit + 205 smoke checks across 9 scripts, all green.
Phase B findings (s2-011)
@indexin oschema annotates the previous field, not the next. Discovered while indexingforge_id— initial placement before the field accidentally indexedkyc_verified_at. Confirmed via existing schemas (consumed_record_index.oschema, usage_record.oschema).mycelium_address: str→Option<String>is forward-compatible (existing rows keep their value, new Forge-only rows omit). Fewuser.mycelium_addresscall sites required.as_deref().resolve_user_from_cookiepreserves their working state without rewrite.D-NN / L-NN
Cross-track touchpoints (Track A)
VmAllocation.forge_idis ready and populated as of s2-011. When Track A wires cockpit-side OAuth, the SSO bridge gates on that field — no further hero_onboarding-side work needed.What's deferred
/account/link-mycelium(mycelium-link to existing Forge user) — ~50 LOC, future phase.display_nameis seeded from Forge login but not editable via UI.References
test pingto Phase 9 — Forge OAuth login + dual-auth (D-18)