docs: record Q12-Q18 (D353-D359) and plan Steam sign-in on the website (D360)
The org lead answered §13's and §14's questions on 2026-10-14: D353 public feed with name and grid, staff adds the Steam id; D354 an event can call a vanilla event, kept at teardown; D355 every world event is a trigger; D356 Safecracker and Scavenger; D357 protocol 14; D358 BotReSpawn stays out; D359 the end walk waits for RaidableBases (against the proposal), recorded in §9 too. D360: Steam sign-in in the same manner as Google and Discord, kept on the website's plan (D171). New website/STEAM_SIGNIN.md (OpenID 2.0 adapter, the build per repo, security, tests, Q1-Q4), indexed in the README, linked from PLAN_REDESIGNS §15 and PLAN_FIXES §4.8, and on the roadmap as the website milestone "Steam sign-in" (website#214). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -36,6 +36,7 @@ sidecar as a service, and hands you the values the website needs.
|
||||
| [WIKI_UPGRADE.md](website/WIKI_UPGRADE.md) | Wiki subsystem upgrade notes |
|
||||
| [SHARD_VISIBILITY.md](website/SHARD_VISIBILITY.md) | Who sees which shard data — the admin-configurable audience framework |
|
||||
| [TRUSTED_DEVICES_MFA.md](website/TRUSTED_DEVICES_MFA.md) | TOTP two-factor, trusted devices and recovery codes |
|
||||
| [STEAM_SIGNIN.md](website/STEAM_SIGNIN.md) | **Plan** — Sign in with Steam as a built-in provider beside Google and Discord (OpenID 2.0), and a Steam id for modules |
|
||||
| [MODERATION_APPEALS.md](website/MODERATION_APPEALS.md) | Moderation actions, content reports and the appeals flow |
|
||||
| [SPAWN_ATLAS.md](website/SPAWN_ATLAS.md) | The bestiary / spawn atlas: what the shard contains, parsed from the shard's own ServUO files — served over the bridge since Protocol 8, so no shared filesystem |
|
||||
| [CLILOCS.md](website/CLILOCS.md) | UO's id → name table, so items have names. The shard decompresses and serves it over the bridge; the desktop conversion it replaced is gone |
|
||||
|
||||
@@ -374,6 +374,9 @@ a display name. The goal the org lead named is server customisation — NPCs wit
|
||||
|
||||
### 4.8 Steam sign-in (moved to the website's plan, D171)
|
||||
|
||||
**Planned 2026-10-14 in [`../../website/STEAM_SIGNIN.md`](../../website/STEAM_SIGNIN.md)** (PLAN_REDESIGNS D360),
|
||||
on the roadmap as the `website` milestone *Steam sign-in*.
|
||||
|
||||
Steam as a sign-in provider beside Google and Discord is part of the base website and is planned there, not
|
||||
here. What this module needs from it: a way to learn a signed-in account's verified Steam id, so linking by
|
||||
Steam can stand beside the in-game code (R1). Steam is OpenID 2.0, not OAuth2/OIDC, so the website needs its
|
||||
|
||||
@@ -11,7 +11,7 @@ chose neither route: Runic Gateway writes its own NPC plugin, RunicNPC, planned
|
||||
[`../../runicnpc/PLAN.md`](../../runicnpc/PLAN.md) (D214–D314), and the rest of this plan waits for it.** RunicNPC v1.0.0 shipped on 2026-10-09, and the plan
|
||||
resumed with §2 the same day (D331–D334); §2 was built on 2026-10-12, and §11's Economics spike the same day
|
||||
raised D335–D336 (§11.2.1). Economics was built on 2026-10-12 too (§11.2.4). The Backpacks spike followed and raised D337–D339 (§11.3.1), and Backpacks was built the same day (D340, D341, §11.3.4). The org lead then added permission rewards (§12,
|
||||
D342–D346), planned and built the same day. On 2026-10-14 RaidableBases was deferred until the current paid copy arrives (D347), with both editions to be supported (D348, §11.4.2). The same day the org lead added Rust's own world events to the base module (§13, D349) and six more first-class optional plugins (§14, D350–D352), to be tested together in one phase before their integration plan. **Every item left is built, tested and PR'd first; every walk —
|
||||
D342–D346), planned and built the same day. On 2026-10-14 RaidableBases was deferred until the current paid copy arrives (D347), with both editions to be supported (D348, §11.4.2). The same day the org lead added Rust's own world events to the base module (§13, D349) and six more first-class optional plugins (§14, D350–D352), to be tested together in one phase before their integration plan. Their questions were answered the same day (D353–D359, the end walk now waiting for RaidableBases), and Steam sign-in stays on the website's plan, linked as §15 (D360). **Every item left is built, tested and PR'd first; every walk —
|
||||
theirs and the ones still owed by §3 and §5 — happens together at the end (D331, §9).**
|
||||
|
||||
This is a companion to [`PLAN_FIXES.md`](PLAN_FIXES.md) and [`PLAN.md`](PLAN.md). Where they disagree, this
|
||||
@@ -29,6 +29,7 @@ document is later and wins. Its decisions continue PLAN_FIXES' numbering at **D1
|
||||
| 12 | Permissions as event rewards: until the event ends, or for good | — (added 2026-10-12) | **core** (website `main`, MODULE_API 1.14.0), Module-Rust |
|
||||
| 13 | Rust's own world events (helicopter, Bradley, cargo, Chinook, airdrops, locked crates) in the base module | — (added 2026-10-14) | Rust-Plugins, Rust-Link, Module-Rust |
|
||||
| 14 | Six more optional plugins: TruePVE, Dynamic PVP, Dangerous Treasures, Guarded Crate, ZLevels, Server Rewards | — (added 2026-10-14) | Rust-Plugins, Rust-Link, Module-Rust, Android-app |
|
||||
| 15 | Steam sign-in: a link to the website's own plan, [`website/STEAM_SIGNIN.md`](../../website/STEAM_SIGNIN.md) | §4.8, D171 | **core** (website), Android-app, Module-Rust |
|
||||
|
||||
**Protocol.** §1, §3, §4 and §5 joined **protocol 13**, which shipped on 2026-10-09 with RunicNPC v1.0.0
|
||||
(bundle 2026.10.09). The wire changes still to come, §2's and §11's, **open protocol 14** on `edge` and are
|
||||
@@ -931,7 +932,8 @@ pulls) is research, not a walk, so it still comes first:
|
||||
**Then one walk, on Oxide and Carbon, of everything still owed (D331):** §2.7; §11's rows; §12.9; §13's and
|
||||
§14's rows; the in-game rows
|
||||
of §5.7 and §3.4; PLAN_FIXES §8's re-walk, including the Carbon pass of the first player walk. What it finds
|
||||
is fixed, protocol 14 is released, and the cutover follows.
|
||||
is fixed, protocol 14 is released, and the cutover follows. **The walk waits for RaidableBases (D359)**, so it
|
||||
runs only once §11.4 is built from the current paid copy (D347).
|
||||
|
||||
## 10. Decisions of record
|
||||
|
||||
@@ -1043,6 +1045,19 @@ The plugin survey the same day led to four more, decided by the org lead on 2026
|
||||
| **D351** | **As few helper plugins as possible, and nothing that runs afoul of a licence.** Runic Gateway never copies, modifies, bundles or redistributes a third-party plugin; it uses only their public hooks, methods and console commands, and operators install them from uMod. | A helper per plugin by default; shipping plugins with the installer or egg. | 14 |
|
||||
| **D352** | **One test phase covers §13 and all six plugins together, on both rigs; then one integration plan, answered before code.** | A spike and a plan per plugin, as §11 did. | 14.2, 14.3 |
|
||||
|
||||
The org lead answered §13's and §14's questions the same day, and added Steam sign-in:
|
||||
|
||||
| # | Decision | Rejected | § |
|
||||
|---|---|---|---|
|
||||
| **D353** | **The public feed shows a world event with its grid and the player's name; the staff feed adds the Steam id** (Q12). | Staff only. | 13.4 |
|
||||
| **D354** | **An event can call a vanilla event** (patrol helicopter, cargo ship, Chinook, airdrop at a grid or position) through a new step whose result is kept (`reversible: 'none'`): nothing is removed when the run ends. A new verb, not an exception to D88 (Q13). | No such step; removing the called event at teardown. | 13.4 |
|
||||
| **D355** | **Every world event in §13.2 is an event-system trigger** (Q14). | Feed only. | 13.4 |
|
||||
| **D356** | **Two new counts with titles: locked crates hacked (*Safecracker*) and airdrops looted first (*Scavenger*)** (Q15). Barrels broken, K/D ratio and time played are not added. | All of PlayerRanks' extra counts. | 13.4 |
|
||||
| **D357** | **§13 and §14 join protocol 14** (D332), released with the rest after the end walk (Q16). | A protocol of their own. | 13.4 |
|
||||
| **D358** | **BotReSpawn stays out** (Q17). | Feed and kill-count support for its bots. | 14.4 |
|
||||
| **D359** | **The end walk waits for RaidableBases** (Q18). D331's one walk runs once every item is coded, §11.4 included, and §11.4 waits for the current paid copy (D347). | Walking the rest without it and giving RaidableBases its own walk later. | 9, 14.4 |
|
||||
| **D360** | **Steam sign-in is supported in the same manner as the other providers:** a built-in provider beside Google and Discord, link-only, through the one session service, configured on Admin → Auth, on the website and in the Android app. **It stays on the website's plan (D171)**, now written as [`website/STEAM_SIGNIN.md`](../../website/STEAM_SIGNIN.md) and on the roadmap as the `website` milestone *Steam sign-in*. §15 links it. | Planning it inside this document. | 15 |
|
||||
|
||||
## 11. First-class optional plugins (D199–D202)
|
||||
|
||||
The org lead named four plugins the module supports as first-class integrations. Kits already is one. The other
|
||||
@@ -1982,6 +1997,8 @@ Kill credit reuses D174's rule, so the feed and *Rotorbane* never disagree about
|
||||
|
||||
### 13.4 Questions for the org lead
|
||||
|
||||
**Answered on 2026-10-14: every proposal below was accepted (D353–D357).**
|
||||
|
||||
- **Q12 — Who sees each line in the feed?** For example, *"Patrol helicopter downed by Alice at G7"* and
|
||||
*"Bob started hacking the locked crate on Large Oil Rig"*. Everyone, or staff only? **Proposed:** the public feed
|
||||
shows the event, the grid and the player's name; the staff feed adds the Steam id. The map already shows
|
||||
@@ -2091,6 +2108,9 @@ PVP, then the other four.
|
||||
|
||||
### 14.4 Questions for the org lead
|
||||
|
||||
**Answered on 2026-10-14:** Q17 as proposed, out (D358). **Q18 against the proposal: the end walk waits for
|
||||
RaidableBases (D359).**
|
||||
|
||||
- **Q17 — Is BotReSpawn in or out?** It is paid ($40) and runs its own NPC AI, profiles and monument spawns,
|
||||
which RunicNPC already does. It cannot drive RunicNPC. The most it could add is its bots named in the feed
|
||||
and kill counts. **Proposed:** out. It can be added later under D348's rules, with a licensed copy for the rigs.
|
||||
@@ -2098,3 +2118,20 @@ PVP, then the other four.
|
||||
RaidableBases now waits for a copy with no date (D347). For example, if the paid copy arrives in a month, do §2,
|
||||
§11.2, §11.3, §12, §13 and §14 wait a month for their walk? **Proposed:** no. The walk runs when §13 and §14
|
||||
are coded, and RaidableBases gets its own walk after its build.
|
||||
|
||||
---
|
||||
|
||||
## 15. Steam sign-in (D360): on the website's plan
|
||||
|
||||
The org lead asked on 2026-10-14 for Steam sign-in **in the same manner as the other providers**, and for it to
|
||||
stay on the website's plan (D171). It is planned in **[`website/STEAM_SIGNIN.md`](../../website/STEAM_SIGNIN.md)**
|
||||
and is on the roadmap as the `website` milestone *Steam sign-in*. It is core work, not an item of this plan, and
|
||||
it does not hold the end walk (D359).
|
||||
|
||||
What this module takes from it:
|
||||
|
||||
- **A verified Steam id for a signed-in account**, read through the module contract (MODULE_API 1.15.0 there).
|
||||
- **A Steam identity as a linked Rust player**, beside the in-game `/link` code (R1). Whether it counts as a link
|
||||
is that plan's Q1, and what unlinking removes is its Q2.
|
||||
- When it lands, Module-Rust's account screens and the link flow are updated in their own PR. Rust's grants
|
||||
already follow the website user to every linked Steam id (D28), so nothing else changes.
|
||||
|
||||
107
website/STEAM_SIGNIN.md
Normal file
107
website/STEAM_SIGNIN.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Sign in with Steam — plan
|
||||
|
||||
> **Status: plan, written 2026-10-14. Nothing is built.** Its four questions (§7) are for the org lead before code.
|
||||
> It is the website's own plan for Steam sign-in, as Rust's
|
||||
> [`PLAN_FIXES.md`](../modules/rust/PLAN_FIXES.md) D171 set out (§4.8 there), and it is on the roadmap as the
|
||||
> `website` milestone *Steam sign-in*, issue website#214. Rust's [`PLAN_REDESIGNS.md`](../modules/rust/PLAN_REDESIGNS.md) §15 links
|
||||
> here for what the Rust module needs from it.
|
||||
|
||||
## 1. Goal
|
||||
|
||||
**Steam becomes a sign-in provider in the same manner as the others** (the org lead, 2026-10-14; D360 in
|
||||
PLAN_REDESIGNS). That means:
|
||||
|
||||
- It is a **built-in provider beside Google and Discord**. An admin turns it on in Admin → Auth.
|
||||
- It follows the **same policy**: link-only, with opt-in sign-up when `player_registration` allows SSO.
|
||||
- It creates sessions through the **same session service**, with the TOTP gate and the trusted-device skip.
|
||||
- It is linked and unlinked from a player's account page, as the others are.
|
||||
- It works **in the Android app** through the existing mobile SSO bridge (M9), like the others.
|
||||
|
||||
What it adds beyond that: **modules can learn a signed-in account's verified Steam ids**. That is what the Rust
|
||||
module has needed since its dry run (`modules/rust-dryrun.md` finding 1, MODULE_SYSTEM.md's "a module cannot
|
||||
register an identity provider").
|
||||
|
||||
Out of scope: a module registering its own identity provider. Steam is core's, so session creation stays a part of
|
||||
core that no module can weaken (MODULE_SYSTEM.md).
|
||||
|
||||
## 2. What exists
|
||||
|
||||
| Piece | Where | Shape |
|
||||
|---|---|---|
|
||||
| Providers | `server/src/auth/providers/` | `registry.js` maps a `kind` to a class. Built-ins `google` and `discord` always exist as defaults; `oidc`/`oauth2` are custom. Each class implements `getAuthorizationUrl(state, options)` and `handleCallback(params)` → `{ subject, email, emailVerified, name }` |
|
||||
| Provider config | `auth_providers` | `kind ENUM('google','discord','oidc','oauth2')`, `client_id`, `client_secret_enc` (AES-256-GCM via `secretBox`), URLs, scopes |
|
||||
| Identities | `user_identities` | `(provider, subject)` unique, `user_id` indexed. **Several identities per provider per user are already allowed**, which several Steam accounts need |
|
||||
| The flow | `router/v1/auth/sso.controller.js` | `beginFlow` (modes `login`, `link`, `mobile`) signs a single-use `sso_tx` cookie. `callback` reads `code` and `state` from the query, verifies the tx, calls `handleCallback`, then `finishLogin` / `finishLink` / `finishMobileLogin`. `provisionSsoPlayer` creates a player when registration allows SSO sign-up |
|
||||
| Android | `/auth/mobile/sso/*` (M9) | A Custom Tab runs the same `/auth/sso/:provider/*` flow; the app receives a one-time code and exchanges it with PKCE |
|
||||
|
||||
## 3. How Steam differs: OpenID 2.0, not OAuth2
|
||||
|
||||
| OAuth2 / OIDC (today) | Steam |
|
||||
|---|---|
|
||||
| Redirect to an authorize URL with `client_id`, `state`, PKCE | Redirect to `https://steamcommunity.com/openid/login` with `openid.mode=checkid_setup`, `openid.return_to`, `openid.realm` and the identifier-select fields. **No client id, no secret, no `state` parameter** |
|
||||
| Callback carries `code` and `state` | Callback carries `openid.*` fields. **Our `state` must ride inside `return_to`**, and the tx cookie verifies it as today |
|
||||
| Exchange the code at the token endpoint | **Verify the assertion** by POSTing the same fields back to Steam with `openid.mode=check_authentication`, and accepting only `is_valid:true` |
|
||||
| `subject` from the ID token or userinfo | `subject` is the 17-digit SteamID64 at the end of `openid.claimed_id` (`https://steamcommunity.com/openid/id/<id>`) |
|
||||
| Email and name from the IdP | **No email.** No name either, unless the site holds a Steam Web API key (Q3) |
|
||||
|
||||
The checks the adapter must make: `openid.op_endpoint` is Steam's, `return_to` matches the URL we sent, the
|
||||
claimed id matches `^https://steamcommunity\.com/openid/id/\d{17}$`, and `check_authentication` says valid. Each
|
||||
assertion is single-use, which the existing single-use tx cookie already enforces.
|
||||
|
||||
## 4. The build
|
||||
|
||||
**Core (`website`):**
|
||||
- `steam.provider.js`, a `BaseProvider` with `getAuthorizationUrl` and `handleCallback` doing §3. It is a built-in in
|
||||
`registry.js` (`{ id: 'steam', kind: 'steam', name: 'Steam' }`), and its config validates with no client id or secret.
|
||||
- `auth_providers.kind` gains `steam`. The schema change is additive.
|
||||
- `sso.controller.js`'s `callback` accepts Steam's `openid.*` fields where it now requires `code`, for `kind: steam`
|
||||
only, and passes them to `handleCallback`. Everything after `handleCallback` is unchanged: `finishLogin`,
|
||||
`finishLink`, `finishMobileLogin`, the TOTP gate and provisioning.
|
||||
- Admin → Auth shows Steam with an enable switch and, per Q3, an optional Web API key stored like a client secret.
|
||||
- The account page lists Steam identities with a *Link Steam* button, and each linked Steam account is shown with its
|
||||
id (or name, Q3) and an *Unlink* button.
|
||||
- **Module contract, MODULE_API 1.15.0 (additions only):** a read of a user's verified identities for one provider,
|
||||
and a hook when one is linked or unlinked. Names are settled at build. Module-uo uses neither, and runs its usual
|
||||
frozen-manifest proof against the new core.
|
||||
- OpenAPI annotations and the regenerated spec for anything new or changed; `BACKEND_DESIGN.md` §3/§4/§6.
|
||||
|
||||
**Android (`Android-app`):** the provider list comes from `/auth/providers`, so Steam appears where the app lists
|
||||
providers. The work is the button's mark and a check of the M9 bridge with Steam's callback. Confirmed in the build.
|
||||
|
||||
**Module-Rust:** a Steam identity on the account becomes a linked Rust player (Q1), beside the in-game `/link` code
|
||||
(R1). Unlinking follows Q2.
|
||||
|
||||
## 5. Security
|
||||
|
||||
- Nothing secret ships to a client. The optional Web API key is server-side and write-only in the API, like client
|
||||
secrets.
|
||||
- The `check_authentication` call is the only proof of a Steam assertion. It is made server to server, and never
|
||||
skipped.
|
||||
- **Link-only stays the rule:** a Steam id that is not linked signs nobody in, unless registration opts into SSO
|
||||
sign-up (Q4).
|
||||
- Login hardening (rate limits, bot score, honeypot) applies to the Steam routes as to the others.
|
||||
|
||||
## 6. Tests
|
||||
|
||||
- Unit: the claimed-id parser, `return_to` matching, a `check_authentication` reply of `is_valid:false`, a replayed
|
||||
assertion, a wrong `op_endpoint`.
|
||||
- Flow: login, link and mobile modes against a stubbed Steam endpoint, including the TOTP gate and provisioning.
|
||||
- Module-uo proof against the new core.
|
||||
- A live walk: sign in with a real Steam account on the local review stack, link, unlink, and the same in the app.
|
||||
|
||||
## 7. Questions for the org lead
|
||||
|
||||
- **Q1 — Does a Steam sign-in count as a linked Rust player?** For example, Alice clicks *Link Steam* on her account
|
||||
page and approves it on steamcommunity.com. Does the Rust module then treat her as owning that Steam account, so
|
||||
her kit rewards and permissions reach it, without the in-game `/link` code? **Proposed:** yes. Steam's own sign-in
|
||||
proves the account at least as well as the code does, and both routes stay.
|
||||
- **Q2 — What does unlinking remove?** For example, Alice unlinks Steam on her account page. Does her Rust link go
|
||||
too, and with it the grants D28 resolves to that Steam id? **Proposed:** the two are one link with two ways to
|
||||
make it, so removing either removes the Rust link. The site says so before it does.
|
||||
- **Q3 — Names and avatars.** Steam's sign-in returns only the Steam id. Showing *"Steam: CoolGuy99"* with an avatar
|
||||
needs a Steam Web API key, which an admin would paste into Admin → Auth like a client secret. **Proposed:** an
|
||||
optional key. Without it the account page shows the Steam id, and a provisioned username is `steam_` plus the
|
||||
last digits of the id.
|
||||
- **Q4 — Sign-up through Steam.** When registration allows SSO sign-up, a new account from Steam has **no email**.
|
||||
**Proposed:** allowed, as for any provider that sends no email. The player adds and verifies an address through
|
||||
the ordinary flow later.
|
||||
Reference in New Issue
Block a user