package main import ( "errors" "fmt" "net" "net/http" "strings" "sync" "time" "gitea.local.lab/Lbenedar/greenlight/internal/data" "gitea.local.lab/Lbenedar/greenlight/internal/validator" "golang.org/x/time/rate" ) func (app *application) recoverPanic(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { w.Header().Set("Connection", "close") app.serverErrorResponse(w, r, fmt.Errorf("%s", err)) } }() next.ServeHTTP(w, r) }) } func (app *application) rateLimit(next http.Handler) http.Handler { type client struct { limiter *rate.Limiter lastSeen time.Time } var ( mu sync.Mutex clients = make(map[string]*client) ) go func() { for { time.Sleep(time.Minute) mu.Lock() for ip, client := range clients { if time.Since(client.lastSeen) > 3*time.Minute { delete(clients, ip) } } mu.Unlock() } }() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if app.config.limiter.enabled { ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { app.serverErrorResponse(w, r, err) return } mu.Lock() if _, found := clients[ip]; !found { clients[ip] = &client{limiter: rate.NewLimiter(rate.Limit(app.config.limiter.rps), app.config.limiter.burst)} } clients[ip].lastSeen = time.Now() if !clients[ip].limiter.Allow() { mu.Unlock() app.rateLimitExceededResponse(w, r) return } mu.Unlock() } next.ServeHTTP(w, r) }) } func (app *application) authenticate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Vary", "Authorization") authorizationHeader := r.Header.Get("Authorization") if authorizationHeader == "" { r = app.contextSetUser(r, data.AnonymousUser) next.ServeHTTP(w, r) return } headerParts := strings.Split(authorizationHeader, " ") if len(headerParts) != 2 || headerParts[0] != "Bearer" { app.invalidCredentialsResponse(w, r) return } token := headerParts[1] v := validator.New() if data.ValidateTokenPlaintext(v, token); !v.Valid() { app.invalidCredentialsResponse(w, r) return } user, err := app.models.Users.GetForToken(data.ScopeAuthentication, token) if err != nil { switch { case errors.Is(err, data.ErrRecordNotFound): app.invalidAuthenticationTokenResponse(w, r) default: app.serverErrorResponse(w, r, err) } return } r = app.contextSetUser(r, user) next.ServeHTTP(w, r) }) }