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/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 instance-wide roles and refine access UI

5b814e9958090cfd1fa826efb2d375bf613547c1 · Keenan Nemetz · 14d ago · parent 9579c1f9

Add instance-wide roles and refine access UI

Instance roles now live on accounts, apply to public repositories, and the Admin role is the unified instance-admin permission with access to every repository. Repository ACLs can raise an instance role but not lower it.

Adds role assignment to the users UI and CLI, open-registration role config, a reusable role picker, and separates adding a repository account from saving access rules.

server/web/auth_handlers.go +23−1

@@ -1,6 +1,7 @@
1 1 package web
2 2
3 3 import (
4 "fmt"
4 5 "net/http"
5 6 "net/url"
6 7 "strings"
@@ -9,6 +10,7 @@ import (
9 10 "github.com/go-webauthn/webauthn/protocol"
10 11 "github.com/go-webauthn/webauthn/webauthn"
11 12
13 "github.com/rodent-software/vole/forge/access"
12 14 "github.com/rodent-software/vole/forge/identity"
13 15 "github.com/rodent-software/vole/forge/session"
14 16 )
@@ -254,11 +256,26 @@ func (w *Web) RegisterPage(rw http.ResponseWriter, r *http.Request) {
254 256 }, 0)
255 257 }
256 258
259 func (w *Web) registrationRole() (access.Role, error) {
260 role, err := access.ParseRole(w.Cfg.OpenRegistrationRole)
261 if err != nil {
262 return access.None, fmt.Errorf("open_registration_role: %w", err)
263 }
264 if role.AtLeast(access.Admin) {
265 return access.None, fmt.Errorf("open_registration_role cannot be admin")
266 }
267 return role, nil
268 }
269
257 270 func (w *Web) RegisterBegin(rw http.ResponseWriter, r *http.Request) {
258 271 if !w.registrationEnabled() {
259 272 writeJSONError(rw, http.StatusServiceUnavailable, "open registration is disabled")
260 273 return
261 274 }
275 if _, err := w.registrationRole(); err != nil {
276 writeJSONError(rw, http.StatusServiceUnavailable, err.Error())
277 return
278 }
262 279 var body struct {
263 280 Username string `json:"username"`
264 281 }
@@ -304,13 +321,18 @@ func (w *Web) RegisterFinish(rw http.ResponseWriter, r *http.Request) {
304 321 writeJSONError(rw, http.StatusBadRequest, "wrong ceremony")
305 322 return
306 323 }
324 role, err := w.registrationRole()
325 if err != nil {
326 writeJSONError(rw, http.StatusInternalServerError, err.Error())
327 return
328 }
307 329 user := &waUser{account: &identity.Account{Name: c.Name, Status: "active"}}
308 330 cred, err := w.Auth.WA.FinishRegistration(user, c.Data, r)
309 331 if err != nil {
310 332 writeJSONError(rw, http.StatusBadRequest, "registration failed: "+err.Error())
311 333 return
312 334 }
313 acct, err := w.Ident.CreatePasskey(c.Name, fromWebAuthn(cred, "first passkey"), "self-registration")
335 acct, err := w.Ident.CreatePasskeyWithRole(c.Name, role, fromWebAuthn(cred, "first passkey"), "self-registration")
314 336 if err != nil {
315 337 writeJSONError(rw, http.StatusConflict, "cannot create the account: "+err.Error())
316 338 return