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.

forge/instance.go +16

@@ -21,6 +21,7 @@ import (
21 21 "github.com/rodent-software/vole/forge/identity"
22 22 "github.com/rodent-software/vole/forge/index"
23 23 "github.com/rodent-software/vole/forge/repo"
24 "github.com/rodent-software/vole/forge/session"
24 25 )
25 26
26 27 // Instance is the state a running or administered Vole instance owns.
@@ -28,6 +29,7 @@ type Instance struct {
28 29 Cfg *config.Config
29 30 Repos *repo.Store
30 31 Identity *identity.Store
32 Sessions *session.Store
31 33 Index *index.Store
32 34 Runs *runs.Store
33 35 ACLs *access.Store
@@ -51,14 +53,23 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
51 53 }
52 54 ident.AuditPath = cfg.AuditLog()
53 55
56 sessions, err := session.Open(cfg.UsersDB())
57 if err != nil {
58 ident.Close()
59 return nil, fmt.Errorf("open sessions: %w", err)
60 }
61 sessions.AuditPath = cfg.AuditLog()
62
54 63 ix, err := index.Open(cfg.IndexDB())
55 64 if err != nil {
65 sessions.Close()
56 66 ident.Close()
57 67 return nil, fmt.Errorf("open index: %w", err)
58 68 }
59 69 rn, err := runs.Open(cfg.RunsDB())
60 70 if err != nil {
61 71 ix.Close()
72 sessions.Close()
62 73 ident.Close()
63 74 return nil, fmt.Errorf("open run queue: %w", err)
64 75 }
@@ -66,6 +77,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
66 77 if err != nil {
67 78 rn.Close()
68 79 ix.Close()
80 sessions.Close()
69 81 ident.Close()
70 82 return nil, fmt.Errorf("open access rules: %w", err)
71 83 }
@@ -76,6 +88,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
76 88 acls.Close()
77 89 rn.Close()
78 90 ix.Close()
91 sessions.Close()
79 92 ident.Close()
80 93 return nil, fmt.Errorf("open locks: %w", err)
81 94 }
@@ -89,6 +102,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
89 102 acls.Close()
90 103 rn.Close()
91 104 ix.Close()
105 sessions.Close()
92 106 ident.Close()
93 107 return nil, err
94 108 }
@@ -97,6 +111,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
97 111 Cfg: cfg,
98 112 Repos: rs,
99 113 Identity: ident,
114 Sessions: sessions,
100 115 Index: ix,
101 116 Runs: rn,
102 117 ACLs: acls,
@@ -130,6 +145,7 @@ func (i *Instance) Close() error {
130 145 i.ACLs.Close,
131 146 i.Runs.Close,
132 147 i.Index.Close,
148 i.Sessions.Close,
133 149 i.Identity.Close,
134 150 } {
135 151 if closeFn == nil {