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 | |