diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index 76bc63e4..732e730c 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -775,13 +775,33 @@ in # the custom theme CSS baked straight into the nix store. STATIC_ROOT_PATH = staticRootWithTheme; }; - # Registration off — operator seeds agent users via - # `nixos-container run hive-forge -- forgejo admin - # user create …`. + # Local sign-up off. An agent's account is made by + # swarm-controller over the admin API, a human's by their + # first authelia login (`oauth2_client` below), which this + # does not block: forgejo's auto-registration checks only + # `ALLOW_ONLY_INTERNAL_REGISTRATION` + # (`routers/web/auth/oauth.go`, 16.0.5). service = { DISABLE_REGISTRATION = true; REQUIRE_SIGNIN_VIEW = false; }; + # A first login through the authelia source creates the + # account, named after authelia's `preferred_username` — the + # authelia username, not the opaque `sub`. + # + # ⚠️ `ACCOUNT_LINKING = login`, never `auto`. A login whose + # name is already taken does not create an account; `auto` + # would then hand the SSO user the existing one. Agents, + # `core` and `swarm-controller` all have local accounts here, + # the last two site admins, and `swarmctl user add` refuses + # none of those names. `login` asks for the existing + # account's own password instead, which an agent's or a + # service account's never leaves the forge. + oauth2_client = { + ENABLE_AUTO_REGISTRATION = true; + USERNAME = "preferred_username"; + ACCOUNT_LINKING = "login"; + }; repository = { DEFAULT_BRANCH = "main"; DEFAULT_PRIVATE = "private"; diff --git a/nix/module-eval/forge-placement.nix b/nix/module-eval/forge-placement.nix index 7776bd2a..02c75288 100644 --- a/nix/module-eval/forge-placement.nix +++ b/nix/module-eval/forge-placement.nix @@ -70,6 +70,8 @@ let tokenFile = m: m.services.hyperhive.deploy.swarm-controller.forgeTokenFile; forgePath = "/var/lib/hyperhive-forge/swarm-controller.token"; + forgeSettings = forgeHere.containers.hive-forge.config.services.forgejo.settings; + cases = [ { # The absence the whole option exists for: a second forge in a swarm @@ -139,6 +141,30 @@ let && tokenFile forgeHere == forgePath && tokenFile allLocal == forgePath; } + { + # The only thing that makes a human's forge account: nothing else + # creates one. + name = "a first authelia login creates the forge account, named by preferred_username"; + ok = + let + o = forgeSettings.oauth2_client; + in + o.ENABLE_AUTO_REGISTRATION == true && o.USERNAME == "preferred_username"; + } + { + # `auto` would give an SSO user whatever local account carries their + # name — an agent's, or `core`'s. + name = "an SSO login adopts an existing forge account only with that account's password"; + ok = forgeSettings.oauth2_client.ACCOUNT_LINKING == "login"; + } + { + # Both halves: local sign-up stays off, and nothing turns on the one + # setting forgejo's auto-registration does check. + name = "local sign-up stays off without blocking the SSO registration"; + ok = + forgeSettings.service.DISABLE_REGISTRATION == true + && !(forgeSettings.service.ALLOW_ONLY_INTERNAL_REGISTRATION or false); + } ]; in runGroup "forge-placement" cases