tenseleyflow/shithub / 828eb18

Browse files

api: wire apilimit + meta into Mount, expose BaseURL/APILimit on Deps

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
828eb184ba854003bc59fb17c7ab0d5939866dca
Parents
8bbbb65
Tree
c396e77

1 changed file

StatusFile+-
M internal/web/handlers/api/api.go 21 0
internal/web/handlers/api/api.gomodified
@@ -24,6 +24,7 @@ import (
24
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
24
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
25
 	"github.com/tenseleyFlow/shithub/internal/ratelimit"
25
 	"github.com/tenseleyFlow/shithub/internal/ratelimit"
26
 	usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
26
 	usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
27
+	"github.com/tenseleyFlow/shithub/internal/web/handlers/api/apilimit"
27
 	"github.com/tenseleyFlow/shithub/internal/web/middleware"
28
 	"github.com/tenseleyFlow/shithub/internal/web/middleware"
28
 )
29
 )
29
 
30
 
@@ -38,6 +39,12 @@ type Deps struct {
38
 	RunnerJWT   *runnerjwt.Signer
39
 	RunnerJWT   *runnerjwt.Signer
39
 	SecretBox   *secretbox.Box
40
 	SecretBox   *secretbox.Box
40
 	RateLimiter *ratelimit.Limiter
41
 	RateLimiter *ratelimit.Limiter
42
+	// BaseURL is the public scheme://host prefix used for absolute
43
+	// pagination Link headers. Empty falls back to path-relative URLs.
44
+	BaseURL string
45
+	// APILimit configures the /api/v1/* rate-limit middleware. Zero
46
+	// values inherit apilimit.Middleware's no-op fallback.
47
+	APILimit apilimit.Config
41
 }
48
 }
42
 
49
 
43
 // Handlers is the registered API handler set. Construct with New.
50
 // Handlers is the registered API handler set. Construct with New.
@@ -77,9 +84,20 @@ const runnerAPIMaxBodyBytes = 768 * 1024
77
 
84
 
78
 // Mount registers /api/v1/* on r. Caller is responsible for putting r
85
 // Mount registers /api/v1/* on r. Caller is responsible for putting r
79
 // in a CSRF-exempt group.
86
 // in a CSRF-exempt group.
87
+//
88
+// Outer middleware on every /api/v1/* request: apilimit stamps the
89
+// X-RateLimit-* headers and refuses over-budget callers with a JSON
90
+// 429. Inner groups attach body caps, PAT auth, and scope decorators
91
+// according to the surface they expose.
80
 func (h *Handlers) Mount(r chi.Router) {
92
 func (h *Handlers) Mount(r chi.Router) {
93
+	apiLimitMW := apilimit.Middleware(h.d.RateLimiter, apilimit.Config{
94
+		AuthedPerHour: h.d.APILimit.AuthedPerHour,
95
+		AnonPerHour:   h.d.APILimit.AnonPerHour,
96
+		Logger:        h.d.Logger,
97
+	})
81
 	r.Group(func(r chi.Router) {
98
 	r.Group(func(r chi.Router) {
82
 		r.Use(middleware.MaxBodySize(runnerAPIMaxBodyBytes))
99
 		r.Use(middleware.MaxBodySize(runnerAPIMaxBodyBytes))
100
+		r.Use(apiLimitMW)
83
 		h.mountRunners(r)
101
 		h.mountRunners(r)
84
 	})
102
 	})
85
 	r.Group(func(r chi.Router) {
103
 	r.Group(func(r chi.Router) {
@@ -88,6 +106,9 @@ func (h *Handlers) Mount(r chi.Router) {
88
 			Pool:      h.d.Pool,
106
 			Pool:      h.d.Pool,
89
 			Debouncer: h.d.Debouncer,
107
 			Debouncer: h.d.Debouncer,
90
 		}))
108
 		}))
109
+		r.Use(apiLimitMW)
110
+		// /meta is capability discovery — no scope required, anon ok.
111
+		h.mountMeta(r)
91
 		r.Group(func(r chi.Router) {
112
 		r.Group(func(r chi.Router) {
92
 			r.Use(middleware.RequireScope(pat.ScopeUserRead))
113
 			r.Use(middleware.RequireScope(pat.ScopeUserRead))
93
 			r.Get("/api/v1/user", h.userMe)
114
 			r.Get("/api/v1/user", h.userMe)