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