this repo has no description

routes: resolve did to handle in middleware

So handles are now purely cosmetic and are resolved to DIDs in the middleware.
auth.ResolveIdent seems to add a lot of latency, so we cache the resolved ident
for 24 hours. It's still not as perfomant but we can come back to this later.

Changed files
+110 -43
routes
+2 -2
routes/git.go
··· 11 11 ) 12 12 13 13 func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) { 14 - name := uniqueName(r) 14 + name := displayRepoName(r) 15 15 name = filepath.Clean(name) 16 16 17 17 repo := filepath.Join(d.c.Repo.ScanPath, name) ··· 32 32 } 33 33 34 34 func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) { 35 - name := uniqueName(r) 35 + name := displayRepoName(r) 36 36 name = filepath.Clean(name) 37 37 38 38 repo := filepath.Join(d.c.Repo.ScanPath, name)
+3 -1
routes/handler.go
··· 9 9 _ "github.com/bluesky-social/indigo/xrpc" 10 10 "github.com/go-chi/chi/v5" 11 11 "github.com/gorilla/sessions" 12 + "github.com/icyphox/bild/auth" 12 13 "github.com/icyphox/bild/config" 13 14 "github.com/icyphox/bild/db" 14 - "github.com/icyphox/bild/routes/auth" 15 + "github.com/icyphox/bild/routes/middleware" 15 16 "github.com/icyphox/bild/routes/tmpl" 16 17 ) 17 18 ··· 77 78 }) 78 79 79 80 r.Route("/@{user}", func(r chi.Router) { 81 + r.Use(middleware.AddDID) 80 82 r.Get("/", h.Index) 81 83 82 84 // Repo routes
+61
routes/middleware/did.go
··· 1 + package middleware 2 + 3 + import ( 4 + "context" 5 + "log" 6 + "net/http" 7 + "sync" 8 + "time" 9 + 10 + "github.com/bluesky-social/indigo/atproto/identity" 11 + "github.com/icyphox/bild/auth" 12 + ) 13 + 14 + type cachedIdent struct { 15 + ident *identity.Identity 16 + expiry time.Time 17 + } 18 + 19 + var ( 20 + identCache = make(map[string]cachedIdent) 21 + cacheMutex sync.RWMutex 22 + ) 23 + 24 + // Only use this middleware for routes that require a handle 25 + // /@{user}/... 26 + func AddDID(next http.Handler) http.Handler { 27 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 28 + user := r.PathValue("user") 29 + 30 + // Check cache first 31 + cacheMutex.RLock() 32 + if cached, ok := identCache[user]; ok && time.Now().Before(cached.expiry) { 33 + cacheMutex.RUnlock() 34 + ctx := context.WithValue(r.Context(), "did", cached.ident.DID.String()) 35 + r = r.WithContext(ctx) 36 + next.ServeHTTP(w, r) 37 + return 38 + } 39 + cacheMutex.RUnlock() 40 + 41 + // Cache miss - resolve and cache 42 + ident, err := auth.ResolveIdent(r.Context(), user) 43 + if err != nil { 44 + log.Println("error resolving identity", err) 45 + http.Error(w, "error resolving identity", http.StatusNotFound) 46 + return 47 + } 48 + 49 + cacheMutex.Lock() 50 + identCache[user] = cachedIdent{ 51 + ident: ident, 52 + expiry: time.Now().Add(24 * time.Hour), 53 + } 54 + cacheMutex.Unlock() 55 + 56 + ctx := context.WithValue(r.Context(), "did", ident.DID.String()) 57 + r = r.WithContext(ctx) 58 + 59 + next.ServeHTTP(w, r) 60 + }) 61 + }
+25 -36
routes/routes.go
··· 22 22 "github.com/google/uuid" 23 23 "github.com/gorilla/sessions" 24 24 shbild "github.com/icyphox/bild/api/bild" 25 + "github.com/icyphox/bild/auth" 25 26 "github.com/icyphox/bild/config" 26 27 "github.com/icyphox/bild/db" 27 28 "github.com/icyphox/bild/git" 28 - "github.com/icyphox/bild/routes/auth" 29 29 "github.com/russross/blackfriday/v2" 30 30 "golang.org/x/crypto/ssh" 31 31 ) ··· 39 39 } 40 40 41 41 func (h *Handle) Index(w http.ResponseWriter, r *http.Request) { 42 - user := chi.URLParam(r, "user") 43 - path := filepath.Join(h.c.Repo.ScanPath, user) 42 + name := displayRepoName(r) 43 + path := filepath.Join(h.c.Repo.ScanPath, name) 44 44 dirs, err := os.ReadDir(path) 45 45 if err != nil { 46 46 h.Write500(w) ··· 75 75 } 76 76 77 77 infos = append(infos, info{ 78 - DisplayName: getDisplayName(name), 78 + DisplayName: trimDotGit(name), 79 79 Name: name, 80 80 Desc: getDescription(path), 81 81 Idle: humanize.Time(c.Author.When), ··· 98 98 } 99 99 100 100 func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 101 - name := uniqueName(r) 101 + name := displayRepoName(r) 102 102 if h.isIgnored(name) { 103 103 h.Write404(w) 104 104 return 105 105 } 106 106 107 - name = filepath.Clean(name) 108 - path := filepath.Join(h.c.Repo.ScanPath, name) 107 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 109 108 110 109 gr, err := git.Open(path, "") 111 110 if err != nil { ··· 164 163 165 164 data := make(map[string]any) 166 165 data["name"] = name 167 - data["displayname"] = getDisplayName(name) 166 + data["displayname"] = trimDotGit(name) 168 167 data["ref"] = mainBranch 169 168 data["readme"] = readmeContent 170 169 data["commits"] = commits ··· 182 181 } 183 182 184 183 func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) { 185 - name := uniqueName(r) 184 + name := displayRepoName(r) 186 185 if h.isIgnored(name) { 187 186 h.Write404(w) 188 187 return ··· 190 189 treePath := chi.URLParam(r, "*") 191 190 ref := chi.URLParam(r, "ref") 192 191 193 - name = filepath.Clean(name) 194 - path := filepath.Join(h.c.Repo.ScanPath, name) 192 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 195 193 gr, err := git.Open(path, ref) 196 194 if err != nil { 197 195 h.Write404(w) ··· 207 205 208 206 data := make(map[string]any) 209 207 data["name"] = name 210 - data["displayname"] = getDisplayName(name) 208 + data["displayname"] = trimDotGit(name) 211 209 data["ref"] = ref 212 210 data["parent"] = treePath 213 211 data["desc"] = getDescription(path) ··· 223 221 raw = rawParam 224 222 } 225 223 226 - name := uniqueName(r) 224 + name := displayRepoName(r) 227 225 228 226 if h.isIgnored(name) { 229 227 h.Write404(w) ··· 232 230 treePath := chi.URLParam(r, "*") 233 231 ref := chi.URLParam(r, "ref") 234 232 235 - name = filepath.Clean(name) 236 - path := filepath.Join(h.c.Repo.ScanPath, name) 233 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 237 234 gr, err := git.Open(path, ref) 238 235 if err != nil { 239 236 h.Write404(w) ··· 247 244 } 248 245 data := make(map[string]any) 249 246 data["name"] = name 250 - data["displayname"] = getDisplayName(name) 247 + data["displayname"] = trimDotGit(name) 251 248 data["ref"] = ref 252 249 data["desc"] = getDescription(path) 253 250 data["path"] = treePath ··· 266 263 } 267 264 268 265 func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 269 - name := uniqueName(r) 266 + name := displayRepoName(r) 270 267 if h.isIgnored(name) { 271 268 h.Write404(w) 272 269 return ··· 288 285 setContentDisposition(w, filename) 289 286 setGZipMIME(w) 290 287 291 - path := filepath.Join(h.c.Repo.ScanPath, name) 288 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 292 289 gr, err := git.Open(path, ref) 293 290 if err != nil { 294 291 h.Write404(w) ··· 317 314 } 318 315 319 316 func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 320 - name := uniqueName(r) 317 + name := displayRepoName(r) 321 318 if h.isIgnored(name) { 322 319 h.Write404(w) 323 320 return 324 321 } 325 322 ref := chi.URLParam(r, "ref") 326 323 327 - path := filepath.Join(h.c.Repo.ScanPath, name) 324 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 328 325 gr, err := git.Open(path, ref) 329 326 if err != nil { 330 327 h.Write404(w) ··· 342 339 data["commits"] = commits 343 340 data["meta"] = h.c.Meta 344 341 data["name"] = name 345 - data["displayname"] = getDisplayName(name) 342 + data["displayname"] = trimDotGit(name) 346 343 data["ref"] = ref 347 344 data["desc"] = getDescription(path) 348 345 data["log"] = true ··· 354 351 } 355 352 356 353 func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 357 - name := uniqueName(r) 354 + name := displayRepoName(r) 358 355 if h.isIgnored(name) { 359 356 h.Write404(w) 360 357 return 361 358 } 362 359 ref := chi.URLParam(r, "ref") 363 360 364 - path := filepath.Join(h.c.Repo.ScanPath, name) 361 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 365 362 gr, err := git.Open(path, ref) 366 363 if err != nil { 367 364 h.Write404(w) ··· 382 379 data["diff"] = diff.Diff 383 380 data["meta"] = h.c.Meta 384 381 data["name"] = name 385 - data["displayname"] = getDisplayName(name) 382 + data["displayname"] = trimDotGit(name) 386 383 data["ref"] = ref 387 384 data["desc"] = getDescription(path) 388 385 ··· 399 396 return 400 397 } 401 398 402 - path := filepath.Join(h.c.Repo.ScanPath, name) 399 + path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 403 400 gr, err := git.Open(path, "") 404 401 if err != nil { 405 402 h.Write404(w) ··· 423 420 424 421 data["meta"] = h.c.Meta 425 422 data["name"] = name 426 - data["displayname"] = getDisplayName(name) 423 + data["displayname"] = trimDotGit(name) 427 424 data["branches"] = branches 428 425 data["tags"] = tags 429 426 data["desc"] = getDescription(path) ··· 550 547 name := r.FormValue("name") 551 548 description := r.FormValue("description") 552 549 553 - repoPath := filepath.Join(h.c.Repo.ScanPath, handle, name) 550 + repoPath := filepath.Join(h.c.Repo.ScanPath, did, name) 554 551 err := git.InitBare(repoPath) 555 - if err != nil { 556 - h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.") 557 - return 558 - } 559 - 560 - // For use by repoguard 561 - didPath := filepath.Join(repoPath, "did") 562 - err = os.WriteFile(didPath, []byte(did), 0644) 563 552 if err != nil { 564 553 h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.") 565 554 return ··· 571 560 return 572 561 } 573 562 574 - w.Header().Set("HX-Redirect", fmt.Sprintf("/@example.com/%s", name)) 563 + w.Header().Set("HX-Redirect", fmt.Sprintf("/@%s/%s", handle, name)) 575 564 w.WriteHeader(http.StatusOK) 576 565 } 577 566 }
+19 -4
routes/util.go
··· 10 10 "strings" 11 11 12 12 "github.com/go-chi/chi/v5" 13 + "github.com/icyphox/bild/auth" 13 14 "github.com/icyphox/bild/git" 14 15 "github.com/microcosm-cc/bluemonday" 15 16 ) ··· 23 24 return err == nil 24 25 } 25 26 26 - func uniqueName(r *http.Request) string { 27 - user := chi.URLParam(r, "user") 27 + func displayRepoName(r *http.Request) string { 28 + user := r.Context().Value("did").(string) 28 29 name := chi.URLParam(r, "name") 29 - return fmt.Sprintf("%s/%s", user, name) 30 + 31 + handle, err := auth.ResolveIdent(r.Context(), user) 32 + if err != nil { 33 + log.Printf("failed to resolve ident: %s: %s", user, err) 34 + return fmt.Sprintf("%s/%s", user, name) 35 + } 36 + 37 + return fmt.Sprintf("@%s/%s", handle.Handle.String(), name) 38 + } 39 + 40 + func didPath(r *http.Request) string { 41 + did := r.Context().Value("did").(string) 42 + path := filepath.Join(did, chi.URLParam(r, "name")) 43 + filepath.Clean(path) 44 + return path 30 45 } 31 46 32 - func getDisplayName(name string) string { 47 + func trimDotGit(name string) string { 33 48 return strings.TrimSuffix(name, ".git") 34 49 } 35 50