vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Add browser auth, contributor access, and gated CI
6c84d387e5f608360e30484a1a5b1cff41076850 · Keenan Nemetz · 16d ago · parent c36d5073
Add browser auth, contributor access, and gated CI Browser sign-in now uses passkeys. Enrollment links are one-time, sessions are hashed in users.db, and issue/comment/approval writes use the session account. HTTPS public_url is required for WebAuthn. Add the contributor role between read and maintain. Contributors can discuss and open change requests, and may push only refs/heads/contrib/<account-id>/. Contributor CI is held per commit until a maintainer allows the exact tip from the repository page. Rename the write role to maintain. write remains accepted as the historical spelling, and access.Write remains as a Go alias so existing callers and ACL rows keep working. Update the web, CLI, queue schema, and guides for the new flows, and cover the role, namespace, auth, and CI-gating paths in tests.
docs/users-and-access.md +92−16
| @@ -1,8 +1,12 @@ | |||
| 1 | 1 | # Users and access | |
| 2 | 2 | ||
| 3 | Vole has no password database and no login screen. Your SSH key is your identity: | ||
| 4 | the same key you use to push. That means one less account system to maintain, and | ||
| 5 | one less password to protect. | ||
| 3 | Vole has no password database. An account has two kinds of credential: | ||
| 4 | |||
| 5 | - an **SSH key** for Git over SSH and remote administration; | ||
| 6 | - a **passkey** for signing in to the browser. | ||
| 7 | |||
| 8 | Both resolve to the same permanent account id, so access rules, approvals, and | ||
| 9 | audit records do not care which one you used. | ||
| 6 | 10 | ||
| 7 | 11 | ## Accounts and keys | |
| 8 | 12 | ||
| @@ -14,9 +18,11 @@ Names are never reused. When you retire an account, its keys stop working and it | |||
| 14 | 18 | name stays reserved. An old reference can never silently point at a different | |
| 15 | 19 | person. | |
| 16 | 20 | ||
| 17 | Account data lives in `data/users.db`. It also holds repository access rules and | ||
| 18 | file locks. Nothing in the file is secret. Vole reloads it every few seconds, so | ||
| 19 | key changes and retirements take effect without restarting the server. | ||
| 21 | Account data lives in `data/users.db`. It also holds passkeys, one-time | ||
| 22 | enrollment links, browser sessions, repository access rules, and file locks. | ||
| 23 | Credentials are public keys and session or invite tokens are stored only as | ||
| 24 | hashes. Vole reloads the roster every few seconds, so key changes and retirements | ||
| 25 | take effect without restarting the server. | ||
| 20 | 26 | ||
| 21 | 27 | ```sh | |
| 22 | 28 | vole admin user create alice --key ~/.ssh/id_ed25519.pub | |
| @@ -28,9 +34,42 @@ vole admin user retire alice | |||
| 28 | 34 | vole admin user backup backup/users.db | |
| 29 | 35 | ``` | |
| 30 | 36 | ||
| 31 | `user create` requires the first key and writes the account and key together. | ||
| 32 | `user add-key` adds another key; use it to rotate credentials without changing | ||
| 33 | the account id that access rules and approvals refer to. | ||
| 37 | `user create --key` writes the account and its first SSH key together. Without | ||
| 38 | `--key` (and with no key on stdin), it creates a browser-only account and prints a | ||
| 39 | one-time enrollment link. `user invite` issues a fresh link for an existing | ||
| 40 | account. `user add-key` adds another SSH key; use it to rotate credentials | ||
| 41 | without changing the account id that access rules and approvals refer to. | ||
| 42 | |||
| 43 | ## Browser sign-in | ||
| 44 | |||
| 45 | Browser login uses a passkey (Touch ID, Windows Hello, a phone, or a security | ||
| 46 | key). It requires `public_url` to be an `https://` URL; Vole refuses to start a | ||
| 47 | WebAuthn ceremony without a canonical secure origin. | ||
| 48 | |||
| 49 | 1. An administrator creates the account and gives the person its one-time | ||
| 50 | enrollment link. | ||
| 51 | 2. The person opens the link and registers a passkey. | ||
| 52 | 3. The passkey creates a browser session. The session cookie is HttpOnly and | ||
| 53 | Secure; Vole stores only its hash. | ||
| 54 | 4. From **Account**, the person can register another passkey or review their | ||
| 55 | sessions. | ||
| 56 | |||
| 57 | A link is single-use and expires. Issuing a new link invalidates the previous | ||
| 58 | one. If the person loses every passkey, an administrator issues a new link; there | ||
| 59 | is no password reset to phish. Retiring an account deletes its sessions | ||
| 60 | immediately, and removing a passkey deletes the sessions that passkey created. | ||
| 61 | |||
| 62 | Admins can inspect and revoke sessions: | ||
| 63 | |||
| 64 | ```sh | ||
| 65 | vole admin user credentials alice | ||
| 66 | vole admin user rm-credential alice <credential-id> | ||
| 67 | vole admin user sessions alice | ||
| 68 | vole admin user revoke-sessions alice | ||
| 69 | vole admin user revoke-sessions --all | ||
| 70 | ``` | ||
| 71 | |||
| 72 | `revoke-sessions --all` is the recovery step after restoring an old `users.db`. | ||
| 34 | 73 | ||
| 35 | 74 | To see which account the current remote's key belongs to: | |
| 36 | 75 | ||
| @@ -76,8 +115,9 @@ accounts. | |||
| 76 | 115 | |---|---| | |
| 77 | 116 | | `none` | No access | | |
| 78 | 117 | | `read` | Clone, fetch, and browse the repository | | |
| 79 | | `write` | Push normal branches and work with issues and change requests | | ||
| 80 | | `admin` | Manage access, push protected branches, override locks, and repair metadata | | ||
| 118 | | `contributor` | Read, file issues and comments, push only `refs/heads/contrib/<account-id>/*`, and open change requests from those branches | | ||
| 119 | | `maintain` | Everything a contributor can do, plus push shared branches, triage issues, and approve change requests | | ||
| 120 | | `admin` | Everything a maintainer can do, plus manage access, push protected branches, override locks, and repair metadata | | ||
| 81 | 121 | ||
| 82 | 122 | A new repository is private. Its default role is `none`, and its owner is an | |
| 83 | 123 | admin. Private repositories return `404` to anonymous visitors, so people cannot | |
| @@ -93,8 +133,9 @@ vole admin acl default demo read | |||
| 93 | 133 | ||
| 94 | 134 | ```sh | |
| 95 | 135 | vole admin acl show demo | |
| 96 | vole admin acl set demo alice write | ||
| 97 | vole admin acl set demo bob read | ||
| 136 | vole admin acl set demo alice maintain | ||
| 137 | vole admin acl set demo bob contributor | ||
| 138 | vole admin acl set demo carol read | ||
| 98 | 139 | vole admin acl default demo read | |
| 99 | 140 | vole admin acl protect demo refs/heads/main | |
| 100 | 141 | vole admin acl approvals demo 2 | |
| @@ -102,7 +143,9 @@ vole admin acl checks demo on | |||
| 102 | 143 | vole admin acl runners demo ci-runner-1 | |
| 103 | 144 | ``` | |
| 104 | 145 | ||
| 105 | - `set` gives one account a role. | ||
| 146 | - `set` gives one account a role. The roles are `none`, `read`, `contributor`, | ||
| 147 | `maintain` and `admin`. `contribute` and `contrib` are accepted as aliases for | ||
| 148 | `contributor`; `write` is accepted as the historical spelling of `maintain`. | ||
| 106 | 149 | - `default` changes the role for everyone else. `read` or higher makes the | |
| 107 | 150 | repository public. | |
| 108 | 151 | - `protect` replaces the list of protected branch patterns. Admins can still push | |
| @@ -113,10 +156,43 @@ vole admin acl runners demo ci-runner-1 | |||
| 113 | 156 | - `runners` names the accounts whose CI results this repository trusts. Admins | |
| 114 | 157 | always count. See [CI](ci.md#who-can-run-jobs-and-report-results). | |
| 115 | 158 | ||
| 159 | ## Contributors and their branches | ||
| 160 | |||
| 161 | The `contributor` role is for open-source projects that accept outside patches. | ||
| 162 | A contributor can read, file issues and comments, and open a change request from | ||
| 163 | the browser. Their Git writes are confined to a branch folder named after their | ||
| 164 | permanent account id: | ||
| 165 | |||
| 166 | ```text | ||
| 167 | refs/heads/contrib/<account-id>/<branch> | ||
| 168 | ``` | ||
| 169 | |||
| 170 | For example, account `01M5JR2JK29KKMAA1T9MDNTPBP` may push | ||
| 171 | `refs/heads/contrib/01M5JR2JK29KKMAA1T9MDNTPBP/fix-docs`. They cannot push | ||
| 172 | `refs/heads/main`, another account's folder, tags, or the `vole` metadata branch. | ||
| 173 | The pre-receive hook checks the namespace, and rebasing or deleting a branch | ||
| 174 | inside their own folder is allowed because it is not shared history. | ||
| 175 | |||
| 176 | The browser's **Open change request** form lists only the branches the account | ||
| 177 | may use as a head. The head commit is resolved from that branch; a contributor | ||
| 178 | cannot name a commit they did not push. A maintainer can approve the request, and | ||
| 179 | landing it still goes through the same protected-branch gate. | ||
| 180 | |||
| 181 | The CLI's `vole issue new`, `vole issue comment` and `vole request new` write the | ||
| 182 | metadata branch directly, so those commands need `maintain`. Contributors use the | ||
| 183 | web for those actions. | ||
| 184 | |||
| 185 | Contributor branches are ordinary branches except for one rule: CI does not run | ||
| 186 | for them automatically. A maintainer or admin must choose **Allow CI for this tip** on | ||
| 187 | the repository page. The approval names the exact commit, so every new push waits | ||
| 188 | again. Treat runner isolation as part of the trust decision before granting | ||
| 189 | `contributor` on a public project. | ||
| 190 | |||
| 116 | 191 | ## When rules are checked | |
| 117 | 192 | ||
| 118 | 193 | Every rule is checked before Vole accepts a push. If a push breaks a rule, none of | |
| 119 | its branches move. | ||
| 194 | its branches move. Browser issue, comment, and approval writes check the same ACL | ||
| 195 | capability and use the same metadata writers and validation as the CLI. | ||
| 120 | 196 | ||
| 121 | 197 | That includes protected branches, approvals, required checks, file size limits, | |
| 122 | 198 | locks, issue formatting, and the CI runner allowlist. Privileged actions — such as | |
| @@ -124,5 +200,5 @@ an admin force-push, a lock override, a user change, or a merge through review | |||
| 124 | 200 | are written to `data/audit.log`. | |
| 125 | 201 | ||
| 126 | 202 | For the reasoning behind this design, see | |
| 127 | [Architecture](architecture.md#trust-your-key-is-your-identity). For file locks, see | ||
| 203 | [Architecture](architecture.md#trust-a-credential-resolves-to-an-account). For file locks, see | ||
| 128 | 204 | [LFS § Locking](lfs.md#locking-files). | |