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.

Allow opt-in open registration

f920a9f12c4ff12e4f47497a084f0201d8914181 · Keenan Nemetz · 16d ago · parent 6c84d387

Allow opt-in open registration

Add open_registration, off by default and effective only with an HTTPS public_url. /register creates an account and its first passkey without an administrator; the account row is written only after the WebAuthn ceremony succeeds, so an abandoned registration reserves no name.

New accounts start with no repository-specific ACL entries. Add self-service SSH key management on the Account page so a registered contributor can add the key needed to push to their contributor namespace.

Cover registration enablement, name validation, deferred account creation, and self-service key add/remove in tests, and document the policy.

forge/identity/passkey.go +28

@@ -83,6 +83,34 @@ type Invite struct {
83 83 Expires time.Time
84 84 }
85 85
86 // CreatePasskey creates a self-registered account and its first passkey in one
87 // transaction. The account id is chosen by the caller because the WebAuthn user
88 // handle is fixed when the ceremony begins; the row is only written when the
89 // authenticator has actually produced a credential, so an abandoned registration
90 // reserves no name.
91 func (s *Store) CreatePasskey(name, accountID string, cred Credential, actor string) (*Account, error) {
92 if err := ValidateName(name); err != nil {
93 return nil, err
94 }
95 if !id.Valid(accountID) {
96 return nil, fmt.Errorf("identity: %q is not a valid account id", accountID)
97 }
98 if err := validateCredential(cred); err != nil {
99 return nil, err
100 }
101 now := time.Now().UTC()
102 err := s.mutate("identity.create", actor, accountID, name+" passkey", func(tx *sql.Tx) error {
103 if err := createAccount(tx, name, accountID, now, nil, nil); err != nil {
104 return err
105 }
106 return insertCredential(tx, accountID, cred, now)
107 })
108 if err != nil {
109 return nil, err
110 }
111 return s.Find(accountID)
112 }
113
86 114 // CreateWithInvite creates an account with no SSH key and an enrollment invite
87 115 // in the same transaction. This is how a browser-only account — somebody who
88 116 // files issues but does not push — comes into existence.