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 browser auth, contributor access, and gated CI

6c84d387e5f608360e30484a1a5b1cff41076850 · Keenan Nemetz · 16d ago · parent c36d5073

Add browser auth, contributor access, and gated CI

Browser sign-in now uses passkeys. Enrollment links are one-time, sessions are hashed in users.db, and issue/comment/approval writes use the session account. HTTPS public_url is required for WebAuthn.

Add the contributor role between read and maintain. Contributors can discuss and open change requests, and may push only refs/heads/contrib/<account-id>/. Contributor CI is held per commit until a maintainer allows the exact tip from the repository page.

Rename the write role to maintain. write remains accepted as the historical spelling, and access.Write remains as a Go alias so existing callers and ACL rows keep working.

Update the web, CLI, queue schema, and guides for the new flows, and cover the role, namespace, auth, and CI-gating paths in tests.

server/ssh/lfs.go +5−5

@@ -12,7 +12,7 @@ import (
12 12 )
13 13
14 14 // serveLFS runs a git-lfs transfer session: `ssh host git-lfs-transfer <repo>
15 // upload|download`. Uploads require write access to the repository, downloads
15 // upload|download`. Uploads require maintain access to the repository, downloads
16 16 // the same read rule the web and clone use — but resolved against the account
17 17 // that owns the key, so a private repository's members can pull objects over
18 18 // SSH while anonymous readers cannot.
@@ -49,10 +49,10 @@ func (s *Server) serveLFS(ch xssh.Channel, req *xssh.Request, pathArg, op, user,
49 49 }
50 50 role := a.RoleFor(accountID)
51 51 canRead := a.Can(accountID, access.Read)
52 canWrite := role.AtLeast(access.Write)
53 if op == "upload" && !canWrite {
52 canMaintain := role.AtLeast(access.Maintain)
53 if op == "upload" && !canMaintain {
54 54 s.cfg.Log.Warn("ssh: refused lfs upload", "user", user, "repo", name, "role", role)
55 refuse("%s does not have write access to %s (role: %s)", user, name, role)
55 refuse("%s does not have maintain access to %s (role: %s)", user, name, role)
56 56 return
57 57 }
58 58 if op == "download" && !canRead {
@@ -72,7 +72,7 @@ func (s *Server) serveLFS(ch xssh.Channel, req *xssh.Request, pathArg, op, user,
72 72 RepoID: m.ID,
73 73 AccountID: accountID,
74 74 CanRead: canRead,
75 CanWrite: canWrite,
75 CanWrite: canMaintain,
76 76 // Releasing somebody else's lock is an admin act, the same rule a force-push
77 77 // follows: the alternative is a lock nobody can clear once its holder leaves.
78 78 IsAdmin: role.AtLeast(access.Admin),