Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Allow opt-in open registration

f920a9f12c4ff12e4f47497a084f0201d8914181 · Keenan Nemetz · 15d 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/auth.go +13−1

@@ -125,6 +125,12 @@ func (w *Web) securityHeaders(rw http.ResponseWriter) {
125 125
126 126 func (w *Web) enabled() bool { return w.Auth != nil && w.Auth.Enabled() }
127 127
128 // registrationEnabled is a policy switch on top of passkey availability: an
129 // instance can have browser login without accepting new accounts.
130 func (w *Web) registrationEnabled() bool {
131 return w.enabled() && w.Cfg != nil && w.Cfg.OpenRegistration
132 }
133
128 134 func (w *Web) accountID(r *http.Request) string {
129 135 if p := w.principal(r); p != nil {
130 136 return p.account.ID
@@ -223,10 +229,11 @@ func writeJSONError(rw http.ResponseWriter, status int, msg string) {
223 229 // ---------- ceremony state ----------
224 230
225 231 type ceremony struct {
226 Kind string // login | invite | passkey
232 Kind string // login | invite | passkey | register
227 233 Data webauthn.SessionData
228 234 AccountID string
229 235 InviteToken string
236 Name string
230 237 Label string
231 238 Expires time.Time
232 239 }
@@ -414,6 +421,11 @@ const authJS = `(function(){
414 421 const c=await navigator.credentials.create({publicKey:prepCreate(b)});
415 422 await post('/auth/invite/finish',resp(c));
416 423 location.href='/';
424 }else if(form.dataset.auth==='register'){
425 const b=await post('/auth/register/begin',{username:form.username.value.trim()});
426 const c=await navigator.credentials.create({publicKey:prepCreate(b)});
427 await post('/auth/register/finish',resp(c));
428 location.href='/';
417 429 }else if(form.dataset.auth==='passkey'){
418 430 const b=await post('/account/passkeys/begin',{label:form.label.value.trim()},true);
419 431 const c=await navigator.credentials.create({publicKey:prepCreate(b)});