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.
server/web/templates.go +49
| @@ -674,8 +674,29 @@ release a description; the tarball below works either way.</p> | |||
| 674 | 674 | <button type="submit">Use a passkey</button> | |
| 675 | 675 | <p class="error" data-auth-error></p> | |
| 676 | 676 | </form> | |
| 677 | {{if .Registration}} | ||
| 678 | <p class="muted">No account yet? <a href="/register">Create one</a>.</p> | ||
| 679 | {{else}} | ||
| 677 | 680 | <p class="muted">No account yet? Ask an administrator for a one-time enrollment link.</p> | |
| 678 | 681 | {{end}} | |
| 682 | {{end}} | ||
| 683 | </div> | ||
| 684 | {{template "footer" .}} | ||
| 685 | {{end}} | ||
| 686 | |||
| 687 | {{define "register"}} | ||
| 688 | {{template "header" .}} | ||
| 689 | <div class="auth card"> | ||
| 690 | <h1>Create an account</h1> | ||
| 691 | <p class="muted">Pick a name and register a passkey. You can add an SSH key after signing in.</p> | ||
| 692 | <form data-auth="register"> | ||
| 693 | <label>Account name | ||
| 694 | <input type="text" name="username" pattern="[a-z0-9][a-z0-9_-]{1,31}" maxlength="32" autocomplete="username webauthn" required autofocus> | ||
| 695 | </label> | ||
| 696 | <button type="submit">Create account and passkey</button> | ||
| 697 | <p class="error" data-auth-error></p> | ||
| 698 | </form> | ||
| 699 | <p class="muted">Names are 2–32 characters: lowercase letters, digits, <code>-</code> and <code>_</code>.</p> | ||
| 679 | 700 | </div> | |
| 680 | 701 | {{template "footer" .}} | |
| 681 | 702 | {{end}} | |
| @@ -709,6 +730,34 @@ release a description; the tarball below works either way.</p> | |||
| 709 | 730 | <h1>Account</h1> | |
| 710 | 731 | <p><strong>{{.Account.Name}}</strong> <span class="muted">{{.Account.ID}}</span></p> | |
| 711 | 732 | ||
| 733 | <h2>SSH keys</h2> | ||
| 734 | {{if .Account.Keys}} | ||
| 735 | <table> | ||
| 736 | <tr><th>Fingerprint</th><th>Type</th><th>Comment</th><th></th></tr> | ||
| 737 | {{range .Account.Keys}} | ||
| 738 | <tr> | ||
| 739 | <td class="sha">{{.Fingerprint}}</td> | ||
| 740 | <td class="muted">{{.Type}}</td> | ||
| 741 | <td class="muted">{{.Comment}}</td> | ||
| 742 | <td> | ||
| 743 | <form method="post" action="/account/keys/remove"> | ||
| 744 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> | ||
| 745 | <input type="hidden" name="fingerprint" value="{{.Fingerprint}}"> | ||
| 746 | <button class="link" type="submit">remove</button> | ||
| 747 | </form> | ||
| 748 | </td> | ||
| 749 | </tr> | ||
| 750 | {{end}} | ||
| 751 | </table> | ||
| 752 | {{else}} | ||
| 753 | <p class="muted">No SSH keys yet.</p> | ||
| 754 | {{end}} | ||
| 755 | <form method="post" action="/account/keys" class="card"> | ||
| 756 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 757 | <label>Public key <textarea name="key" rows="3" placeholder="ssh-ed25519 AAAA... you@example.com" required></textarea></label> | ||
| 758 | <button type="submit">Add SSH key</button> | ||
| 759 | </form> | ||
| 760 | |||
| 712 | 761 | <h2>Passkeys</h2> | |
| 713 | 762 | {{if .Credentials}} | |
| 714 | 763 | <table> | |