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.
Identify accounts by their registered name
62576fa8157d431dcf2d360f3ebe481de77bda03 · Keenan Nemetz · 14d ago · parent 642e5994
Identify accounts by their registered name Drop the internal account id and display name. Users, keys, passkeys, invites, sessions, ACL entries, approvals, locks and CI records now all use the registered name as the single identity. The roster migrates in place: old id-keyed tables are rebuilt around the name, and access-rule/lock references are rewritten through the same mapping. Display-name editing and whoami --id are removed.
server/web/writes.go +27−27
| @@ -38,7 +38,7 @@ func (w *Web) CreateIssue(rw http.ResponseWriter, r *http.Request) { | |||
| 38 | 38 | return | |
| 39 | 39 | } | |
| 40 | 40 | acl, err := m.ACL() | |
| 41 | if err != nil || !acl.CanDiscuss(pr.account.ID) { | ||
| 41 | if err != nil || !acl.CanDiscuss(pr.account.Name) { | ||
| 42 | 42 | http.Error(rw, "you do not have permission to file issues here", http.StatusForbidden) | |
| 43 | 43 | return | |
| 44 | 44 | } | |
| @@ -68,7 +68,7 @@ func (w *Web) CreateIssue(rw http.ResponseWriter, r *http.Request) { | |||
| 68 | 68 | w.serverError(rw, r, "cannot create the issue: "+err.Error()) | |
| 69 | 69 | return | |
| 70 | 70 | } | |
| 71 | w.audit("web.issue.create", pr.account.ID, m.Name+" "+iss.ID) | ||
| 71 | w.audit("web.issue.create", pr.account.Name, m.Name+" "+iss.ID) | ||
| 72 | 72 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 73 | 73 | w.Logger("web: issue %s left the metadata tree invalid: %v", iss.ID, problems) | |
| 74 | 74 | } | |
| @@ -99,7 +99,7 @@ func (w *Web) SetIssueLabels(rw http.ResponseWriter, r *http.Request) { | |||
| 99 | 99 | return | |
| 100 | 100 | } | |
| 101 | 101 | acl, err := m.ACL() | |
| 102 | if err != nil || (!acl.CanTriage(pr.account.ID) && iss.Author != pr.account.Name) { | ||
| 102 | if err != nil || (!acl.CanTriage(pr.account.Name) && iss.Author != pr.account.Name) { | ||
| 103 | 103 | http.Error(rw, "you do not have permission to edit these labels", http.StatusForbidden) | |
| 104 | 104 | return | |
| 105 | 105 | } | |
| @@ -123,7 +123,7 @@ func (w *Web) SetIssueLabels(rw http.ResponseWriter, r *http.Request) { | |||
| 123 | 123 | http.Error(rw, "cannot update the labels: "+err.Error(), http.StatusBadRequest) | |
| 124 | 124 | return | |
| 125 | 125 | } | |
| 126 | w.audit("web.issue.labels", pr.account.ID, m.Name+" "+id+" "+strings.Join(labels, ",")) | ||
| 126 | w.audit("web.issue.labels", pr.account.Name, m.Name+" "+id+" "+strings.Join(labels, ",")) | ||
| 127 | 127 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 128 | 128 | w.Logger("web: issue labels %s left the metadata tree invalid: %v", id, problems) | |
| 129 | 129 | } | |
| @@ -160,7 +160,7 @@ func (w *Web) SetIssueAssignees(rw http.ResponseWriter, r *http.Request) { | |||
| 160 | 160 | return | |
| 161 | 161 | } | |
| 162 | 162 | acl, err := m.ACL() | |
| 163 | if err != nil || (!acl.CanTriage(pr.account.ID) && iss.Author != pr.account.Name) { | ||
| 163 | if err != nil || (!acl.CanTriage(pr.account.Name) && iss.Author != pr.account.Name) { | ||
| 164 | 164 | http.Error(rw, "you do not have permission to edit these assignees", http.StatusForbidden) | |
| 165 | 165 | return | |
| 166 | 166 | } | |
| @@ -195,7 +195,7 @@ func (w *Web) SetIssueAssignees(rw http.ResponseWriter, r *http.Request) { | |||
| 195 | 195 | http.Error(rw, "cannot update the assignees: "+err.Error(), http.StatusBadRequest) | |
| 196 | 196 | return | |
| 197 | 197 | } | |
| 198 | w.audit("web.issue.assignees", pr.account.ID, m.Name+" "+id+" "+strings.Join(assignees, ",")) | ||
| 198 | w.audit("web.issue.assignees", pr.account.Name, m.Name+" "+id+" "+strings.Join(assignees, ",")) | ||
| 199 | 199 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 200 | 200 | w.Logger("web: issue assignees %s left the metadata tree invalid: %v", id, problems) | |
| 201 | 201 | } | |
| @@ -224,7 +224,7 @@ func (w *Web) AddIssueComment(rw http.ResponseWriter, r *http.Request) { | |||
| 224 | 224 | return | |
| 225 | 225 | } | |
| 226 | 226 | acl, err := m.ACL() | |
| 227 | if err != nil || !acl.CanDiscuss(pr.account.ID) { | ||
| 227 | if err != nil || !acl.CanDiscuss(pr.account.Name) { | ||
| 228 | 228 | http.Error(rw, "you do not have permission to comment here", http.StatusForbidden) | |
| 229 | 229 | return | |
| 230 | 230 | } | |
| @@ -267,7 +267,7 @@ func (w *Web) AddIssueComment(rw http.ResponseWriter, r *http.Request) { | |||
| 267 | 267 | http.Error(rw, "cannot add the comment: "+err.Error(), http.StatusBadRequest) | |
| 268 | 268 | return | |
| 269 | 269 | } | |
| 270 | w.audit("web.issue.comment", pr.account.ID, m.Name+" "+id+" "+c.ID) | ||
| 270 | w.audit("web.issue.comment", pr.account.Name, m.Name+" "+id+" "+c.ID) | ||
| 271 | 271 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 272 | 272 | w.Logger("web: comment %s left the metadata tree invalid: %v", c.ID, problems) | |
| 273 | 273 | } | |
| @@ -290,7 +290,7 @@ func canManageComment(pr *principal, acl *access.ACL, author string) bool { | |||
| 290 | 290 | if pr.account.Name == author { | |
| 291 | 291 | return true | |
| 292 | 292 | } | |
| 293 | return acl != nil && acl.RoleFor(pr.account.ID).AtLeast(access.Admin) | ||
| 293 | return acl != nil && acl.RoleFor(pr.account.Name).AtLeast(access.Admin) | ||
| 294 | 294 | } | |
| 295 | 295 | ||
| 296 | 296 | func issueCommentByID(iss *metadata.Issue, id string) *metadata.Comment { | |
| @@ -368,7 +368,7 @@ func (w *Web) EditIssueComment(rw http.ResponseWriter, r *http.Request) { | |||
| 368 | 368 | if pr.account.Name != c.Author { | |
| 369 | 369 | detail += " (author " + c.Author + ", admin override)" | |
| 370 | 370 | } | |
| 371 | w.audit("web.issue.comment.edit", pr.account.ID, detail) | ||
| 371 | w.audit("web.issue.comment.edit", pr.account.Name, detail) | ||
| 372 | 372 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 373 | 373 | w.Logger("web: edit comment %s left the metadata tree invalid: %v", commentID, problems) | |
| 374 | 374 | } | |
| @@ -426,7 +426,7 @@ func (w *Web) DeleteIssueComment(rw http.ResponseWriter, r *http.Request) { | |||
| 426 | 426 | if pr.account.Name != c.Author { | |
| 427 | 427 | detail += " (author " + c.Author + ", admin override)" | |
| 428 | 428 | } | |
| 429 | w.audit("web.issue.comment.delete", pr.account.ID, detail) | ||
| 429 | w.audit("web.issue.comment.delete", pr.account.Name, detail) | ||
| 430 | 430 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 431 | 431 | w.Logger("web: delete comment %s left the metadata tree invalid: %v", commentID, problems) | |
| 432 | 432 | } | |
| @@ -457,7 +457,7 @@ func (w *Web) ApproveRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 457 | 457 | return | |
| 458 | 458 | } | |
| 459 | 459 | acl, err := m.ACL() | |
| 460 | if err != nil || !acl.CanApprove(pr.account.ID) { | ||
| 460 | if err != nil || !acl.CanApprove(pr.account.Name) { | ||
| 461 | 461 | http.Error(rw, "you do not have permission to approve here", http.StatusForbidden) | |
| 462 | 462 | return | |
| 463 | 463 | } | |
| @@ -471,11 +471,11 @@ func (w *Web) ApproveRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 471 | 471 | http.Error(rw, "this change request is not open", http.StatusBadRequest) | |
| 472 | 472 | return | |
| 473 | 473 | } | |
| 474 | if req.AuthorID == pr.account.ID { | ||
| 474 | if req.Author == pr.account.Name { | ||
| 475 | 475 | http.Error(rw, "you cannot approve your own change request", http.StatusForbidden) | |
| 476 | 476 | return | |
| 477 | 477 | } | |
| 478 | if _, err := metadata.ApproveRequest(m.Git(), id, pr.account.ID, time.Now().UTC(), pr.account.Name); err != nil { | ||
| 478 | if _, err := metadata.ApproveRequest(m.Git(), id, pr.account.Name, time.Now().UTC(), pr.account.Name); err != nil { | ||
| 479 | 479 | if htmxRequest(r) { | |
| 480 | 480 | w.render(rw, "request_sidebar", w.requestSidebar(r, m, req, "", err.Error()), 0) | |
| 481 | 481 | return | |
| @@ -483,7 +483,7 @@ func (w *Web) ApproveRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 483 | 483 | http.Error(rw, "cannot approve: "+err.Error(), http.StatusBadRequest) | |
| 484 | 484 | return | |
| 485 | 485 | } | |
| 486 | w.audit("web.request.approve", pr.account.ID, m.Name+" "+id+" "+req.Head) | ||
| 486 | w.audit("web.request.approve", pr.account.Name, m.Name+" "+id+" "+req.Head) | ||
| 487 | 487 | if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 488 | 488 | w.Logger("web: approval left the request tree invalid: %v", problems) | |
| 489 | 489 | } | |
| @@ -532,7 +532,7 @@ func (w *Web) CreateChangeRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 532 | 532 | return | |
| 533 | 533 | } | |
| 534 | 534 | acl, err := m.ACL() | |
| 535 | if err != nil || !acl.CanOpenRequest(pr.account.ID) { | ||
| 535 | if err != nil || !acl.CanOpenRequest(pr.account.Name) { | ||
| 536 | 536 | http.Error(rw, "you do not have permission to open change requests here", http.StatusForbidden) | |
| 537 | 537 | return | |
| 538 | 538 | } | |
| @@ -554,7 +554,7 @@ func (w *Web) CreateChangeRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 554 | 554 | return | |
| 555 | 555 | } | |
| 556 | 556 | headRef := "refs/heads/" + head | |
| 557 | if acl.RoleFor(pr.account.ID) == access.Contributor { | ||
| 557 | if acl.RoleFor(pr.account.Name) == access.Contributor { | ||
| 558 | 558 | // A contributor's head must be a branch the hook would let them push. The | |
| 559 | 559 | // namespace is the account name, which cannot change, so this agrees with | |
| 560 | 560 | // the gate without a roster lookup. | |
| @@ -572,7 +572,7 @@ func (w *Web) CreateChangeRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 572 | 572 | http.Error(rw, "that base branch does not exist", http.StatusBadRequest) | |
| 573 | 573 | return | |
| 574 | 574 | } | |
| 575 | req, err := metadata.NewRequest(title, body, pr.account.Name, pr.account.ID, base, headCommit, time.Now().UTC()) | ||
| 575 | req, err := metadata.NewRequest(title, body, pr.account.Name, base, headCommit, time.Now().UTC()) | ||
| 576 | 576 | if err != nil { | |
| 577 | 577 | http.Error(rw, err.Error(), http.StatusBadRequest) | |
| 578 | 578 | return | |
| @@ -581,7 +581,7 @@ func (w *Web) CreateChangeRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 581 | 581 | w.serverError(rw, r, "cannot open the change request: "+err.Error()) | |
| 582 | 582 | return | |
| 583 | 583 | } | |
| 584 | w.audit("web.request.create", pr.account.ID, m.Name+" "+req.ID+" "+headCommit) | ||
| 584 | w.audit("web.request.create", pr.account.Name, m.Name+" "+req.ID+" "+headCommit) | ||
| 585 | 585 | if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 586 | 586 | w.Logger("web: request %s left the request tree invalid: %v", req.ID, problems) | |
| 587 | 587 | } | |
| @@ -621,7 +621,7 @@ func (w *Web) ChangeIssueState(rw http.ResponseWriter, r *http.Request) { | |||
| 621 | 621 | return | |
| 622 | 622 | } | |
| 623 | 623 | acl, err := m.ACL() | |
| 624 | if err != nil || (!acl.CanTriage(pr.account.ID) && iss.Author != pr.account.Name) { | ||
| 624 | if err != nil || (!acl.CanTriage(pr.account.Name) && iss.Author != pr.account.Name) { | ||
| 625 | 625 | http.Error(rw, "you do not have permission to change this issue", http.StatusForbidden) | |
| 626 | 626 | return | |
| 627 | 627 | } | |
| @@ -630,7 +630,7 @@ func (w *Web) ChangeIssueState(rw http.ResponseWriter, r *http.Request) { | |||
| 630 | 630 | w.serverError(rw, r, "cannot change the issue: "+err.Error()) | |
| 631 | 631 | return | |
| 632 | 632 | } | |
| 633 | w.audit("web.issue.state", pr.account.ID, m.Name+" "+id+" "+state) | ||
| 633 | w.audit("web.issue.state", pr.account.Name, m.Name+" "+id+" "+state) | ||
| 634 | 634 | if problems := metadata.ValidateIssues(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 635 | 635 | w.Logger("web: issue state %s left the metadata tree invalid: %v", id, problems) | |
| 636 | 636 | } | |
| @@ -655,7 +655,7 @@ func (w *Web) AddRequestComment(rw http.ResponseWriter, r *http.Request) { | |||
| 655 | 655 | return | |
| 656 | 656 | } | |
| 657 | 657 | acl, err := m.ACL() | |
| 658 | if err != nil || !acl.CanDiscuss(pr.account.ID) { | ||
| 658 | if err != nil || !acl.CanDiscuss(pr.account.Name) { | ||
| 659 | 659 | http.Error(rw, "you do not have permission to comment here", http.StatusForbidden) | |
| 660 | 660 | return | |
| 661 | 661 | } | |
| @@ -727,7 +727,7 @@ func (w *Web) AddRequestComment(rw http.ResponseWriter, r *http.Request) { | |||
| 727 | 727 | http.Error(rw, "cannot add the comment: "+err.Error(), http.StatusBadRequest) | |
| 728 | 728 | return | |
| 729 | 729 | } | |
| 730 | w.audit("web.request.comment", pr.account.ID, m.Name+" "+id+" "+c.ID) | ||
| 730 | w.audit("web.request.comment", pr.account.Name, m.Name+" "+id+" "+c.ID) | ||
| 731 | 731 | if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 732 | 732 | w.Logger("web: request comment %s left the request tree invalid: %v", c.ID, problems) | |
| 733 | 733 | } | |
| @@ -807,7 +807,7 @@ func (w *Web) EditRequestComment(rw http.ResponseWriter, r *http.Request) { | |||
| 807 | 807 | if pr.account.Name != c.Author { | |
| 808 | 808 | detail += " (author " + c.Author + ", admin override)" | |
| 809 | 809 | } | |
| 810 | w.audit("web.request.comment.edit", pr.account.ID, detail) | ||
| 810 | w.audit("web.request.comment.edit", pr.account.Name, detail) | ||
| 811 | 811 | if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 812 | 812 | w.Logger("web: edit comment %s left the metadata tree invalid: %v", commentID, problems) | |
| 813 | 813 | } | |
| @@ -865,7 +865,7 @@ func (w *Web) DeleteRequestComment(rw http.ResponseWriter, r *http.Request) { | |||
| 865 | 865 | if pr.account.Name != c.Author { | |
| 866 | 866 | detail += " (author " + c.Author + ", admin override)" | |
| 867 | 867 | } | |
| 868 | w.audit("web.request.comment.delete", pr.account.ID, detail) | ||
| 868 | w.audit("web.request.comment.delete", pr.account.Name, detail) | ||
| 869 | 869 | if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 870 | 870 | w.Logger("web: delete comment %s left the metadata tree invalid: %v", commentID, problems) | |
| 871 | 871 | } | |
| @@ -905,7 +905,7 @@ func (w *Web) ChangeRequestState(rw http.ResponseWriter, r *http.Request) { | |||
| 905 | 905 | return | |
| 906 | 906 | } | |
| 907 | 907 | acl, err := m.ACL() | |
| 908 | if err != nil || (!acl.CanTriage(pr.account.ID) && req.AuthorID != pr.account.ID) { | ||
| 908 | if err != nil || (!acl.CanTriage(pr.account.Name) && req.Author != pr.account.Name) { | ||
| 909 | 909 | http.Error(rw, "you do not have permission to change this change request", http.StatusForbidden) | |
| 910 | 910 | return | |
| 911 | 911 | } | |
| @@ -922,7 +922,7 @@ func (w *Web) ChangeRequestState(rw http.ResponseWriter, r *http.Request) { | |||
| 922 | 922 | w.serverError(rw, r, "cannot change the change request: "+err.Error()) | |
| 923 | 923 | return | |
| 924 | 924 | } | |
| 925 | w.audit("web.request.state", pr.account.ID, m.Name+" "+id+" "+state) | ||
| 925 | w.audit("web.request.state", pr.account.Name, m.Name+" "+id+" "+state) | ||
| 926 | 926 | if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 { | |
| 927 | 927 | w.Logger("web: request state %s left the request tree invalid: %v", id, problems) | |
| 928 | 928 | } | |