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/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/auth.go +20−22

@@ -3,8 +3,8 @@ package web
3 3 // Browser authentication: passkey ceremonies, sessions and CSRF.
4 4 //
5 5 // The browser is a second credential for an account that already exists; it is
6 // not a second account. A passkey assertion resolves to the same account id an
7 // SSH key does, and every write still goes through that id.
6 // not a second account. A passkey assertion resolves to the same account name an
7 // SSH key does, and every write still goes through that name.
8 8
9 9 import (
10 10 "context"
@@ -132,7 +132,7 @@ func (w *Web) Middleware(next http.Handler) http.Handler {
132 132 if w.Sessions != nil {
133 133 sess, cookieName := w.sessionFromCookie(r)
134 134 if sess != nil {
135 if acct, err := w.Ident.Find(sess.AccountID); err == nil && acct.Active() {
135 if acct, err := w.Ident.Find(sess.Name); err == nil && acct.Active() {
136 136 r = w.withPrincipal(r, acct, sess)
137 137 } else {
138 138 w.clearCookieFor(rw, cookieName, cookieName != devSessionCookieName)
@@ -144,7 +144,7 @@ func (w *Web) Middleware(next http.Handler) http.Handler {
144 144 // only after an explicit logout has not opted out for this browser.
145 145 if w.principal(r) == nil && w.devLoginRequest(r) {
146 146 if sess := w.ensureDevSession(); sess != nil {
147 if acct, err := w.Ident.Find(sess.AccountID); err == nil && acct.Active() {
147 if acct, err := w.Ident.Find(sess.Name); err == nil && acct.Active() {
148 148 w.setDevSessionCookie(rw, sess)
149 149 r = w.withPrincipal(r, acct, sess)
150 150 }
@@ -237,7 +237,7 @@ func (w *Web) ensureDevSession() *session.Session {
237 237 return sess
238 238 }
239 239 }
240 sess, err := w.Sessions.Create(w.devAccount.ID, nil, "dev-login")
240 sess, err := w.Sessions.Create(w.devAccount.Name, nil, "dev-login")
241 241 if err != nil {
242 242 w.Logger("dev login: create session: %v", err)
243 243 return nil
@@ -296,41 +296,41 @@ func (w *Web) registrationEnabled() bool {
296 296 return w.enabled() && w.Cfg != nil && w.Cfg.OpenRegistration
297 297 }
298 298
299 func (w *Web) accountID(r *http.Request) string {
299 func (w *Web) accountName(r *http.Request) string {
300 300 if p := w.principal(r); p != nil {
301 return p.account.ID
301 return p.account.Name
302 302 }
303 303 return ""
304 304 }
305 305
306 306 func (w *Web) canDiscuss(r *http.Request, acl *access.ACL) bool {
307 id := w.accountID(r)
308 return id != "" && acl.CanDiscuss(id)
307 name := w.accountName(r)
308 return name != "" && acl.CanDiscuss(name)
309 309 }
310 310
311 func (w *Web) canApprove(r *http.Request, acl *access.ACL, authorID string) bool {
312 id := w.accountID(r)
313 return id != "" && id != authorID && acl.CanApprove(id)
311 func (w *Web) canApprove(r *http.Request, acl *access.ACL, author string) bool {
312 name := w.accountName(r)
313 return name != "" && name != author && acl.CanApprove(name)
314 314 }
315 315
316 316 func (w *Web) page(r *http.Request, title string) Page {
317 317 p := Page{Title: title}
318 318 if pr := w.principal(r); pr != nil {
319 319 p.LoggedIn = true
320 p.User = pr.account.Label()
321 p.AccountID = pr.account.ID
320 p.User = pr.account.Name
321 p.AccountName = pr.account.Name
322 322 p.CSRF = pr.session.CSRF
323 323 }
324 324 if w.devAccount != nil {
325 325 p.DevLogin = w.devAccount.Name
326 326 }
327 327 if p.LoggedIn && w.Ident != nil {
328 p.InstanceAdmin = w.Ident.IsAdmin(p.AccountID)
328 p.InstanceAdmin = w.Ident.IsAdmin(p.AccountName)
329 329 }
330 330 if p.LoggedIn && w.Repos != nil {
331 331 if name := r.PathValue("name"); name != "" {
332 332 if m, err := w.Repos.Open(name); err == nil {
333 if acl, err := m.ACL(); err == nil && acl.Can(p.AccountID, access.Admin) {
333 if acl, err := m.ACL(); err == nil && acl.Can(p.AccountName, access.Admin) {
334 334 p.CanAdmin = true
335 335 }
336 336 }
@@ -429,10 +429,8 @@ func writeJSONError(rw http.ResponseWriter, status int, msg string) {
429 429 type ceremony struct {
430 430 Kind string // login | invite | passkey | register
431 431 Data webauthn.SessionData
432 AccountID string
433 InviteToken string
434 432 Name string
435 Display string
433 InviteToken string
436 434 Label string
437 435 Expires time.Time
438 436 }
@@ -519,13 +517,13 @@ type waUser struct {
519 517 creds []webauthn.Credential
520 518 }
521 519
522 func (u *waUser) WebAuthnID() []byte { return []byte(u.account.ID) }
520 func (u *waUser) WebAuthnID() []byte { return []byte(u.account.Name) }
523 521 func (u *waUser) WebAuthnName() string { return u.account.Name }
524 522 func (u *waUser) WebAuthnDisplayName() string { return u.account.Name }
525 523 func (u *waUser) WebAuthnCredentials() []webauthn.Credential { return u.creds }
526 524
527 525 func (w *Web) waUser(a *identity.Account) (*waUser, error) {
528 creds, err := w.Ident.Credentials(a.ID)
526 creds, err := w.Ident.Credentials(a.Name)
529 527 if err != nil {
530 528 return nil, err
531 529 }
@@ -621,7 +619,7 @@ const authJS = `(function(){
621 619 await post('/auth/invite/finish',resp(c));
622 620 location.href='/';
623 621 }else if(form.dataset.auth==='register'){
624 const b=await post('/auth/register/begin',{username:form.username.value.trim(),display:form.display?form.display.value.trim():''});
622 const b=await post('/auth/register/begin',{username:form.username.value.trim()});
625 623 const c=await navigator.credentials.create({publicKey:prepCreate(b)});
626 624 await post('/auth/register/finish',resp(c));
627 625 location.href='/';