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.
Allow opt-in open registration
f920a9f12c4ff12e4f47497a084f0201d8914181 · Keenan Nemetz · 16d ago · parent 6c84d387
Allow opt-in open registration Add open_registration, off by default and effective only with an HTTPS public_url. /register creates an account and its first passkey without an administrator; the account row is written only after the WebAuthn ceremony succeeds, so an abandoned registration reserves no name. New accounts start with no repository-specific ACL entries. Add self-service SSH key management on the Account page so a registered contributor can add the key needed to push to their contributor namespace. Cover registration enablement, name validation, deferred account creation, and self-service key add/remove in tests, and document the policy.
forge/identity/passkey_test.go +27
| @@ -282,3 +282,30 @@ VALUES(?, ?, ?, 1, 1, 9999999999)`, "hash-"+credentialIDString(id), a.ID, creden | |||
| 282 | 282 | t.Fatalf("removing one passkey removed another's session (count=%d)", n) | |
| 283 | 283 | } | |
| 284 | 284 | } | |
| 285 | |||
| 286 | func TestCreatePasskeySelfRegistration(t *testing.T) { | ||
| 287 | t.Parallel() | ||
| 288 | s := newStore(t) | ||
| 289 | id := "01M5JR2JK29KKMAA1T9MDNTPBP" | ||
| 290 | cred := testCredential(t, "first passkey") | ||
| 291 | a, err := s.CreatePasskey("alice", id, cred, "self-registration") | ||
| 292 | if err != nil { | ||
| 293 | t.Fatal(err) | ||
| 294 | } | ||
| 295 | if a.ID != id || a.Name != "alice" || len(a.Keys) != 0 { | ||
| 296 | t.Fatalf("created account = %+v", a) | ||
| 297 | } | ||
| 298 | creds, err := s.Credentials(a.ID) | ||
| 299 | if err != nil || len(creds) != 1 || string(creds[0].ID) != string(cred.ID) { | ||
| 300 | t.Fatalf("credentials = %+v, %v", creds, err) | ||
| 301 | } | ||
| 302 | |||
| 303 | // A second registration for the same name must not leave an account behind. | ||
| 304 | other := "01M5JR2JK29KKMAA1T9MDNTPBQ" | ||
| 305 | if _, err := s.CreatePasskey("alice", other, testCredential(t, "other"), "self-registration"); err == nil { | ||
| 306 | t.Fatal("duplicate name was accepted") | ||
| 307 | } | ||
| 308 | if _, err := s.Find(other); err == nil { | ||
| 309 | t.Fatal("failed registration left an account behind") | ||
| 310 | } | ||
| 311 | } | ||