feat: CRUD rate limit and improve security
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m18s
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m18s
This commit is contained in:
parent
f0d5cc459a
commit
696cb6a2fa
5 changed files with 144 additions and 42 deletions
|
|
@ -78,6 +78,20 @@ func (h *SpaceHandler) getListForSpace(w http.ResponseWriter, spaceID, listID st
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTagForSpace fetches a tag and verifies it belongs to the given space.
|
||||||
|
func (h *SpaceHandler) getTagForSpace(w http.ResponseWriter, spaceID, tagID string) *model.Tag {
|
||||||
|
tag, err := h.tagService.GetTagByID(tagID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Tag not found", http.StatusNotFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tag.SpaceID != spaceID {
|
||||||
|
http.Error(w, "Not Found", http.StatusNotFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
func (h *SpaceHandler) DashboardPage(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) DashboardPage(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
space, err := h.spaceService.GetSpace(spaceID)
|
space, err := h.spaceService.GetSpace(spaceID)
|
||||||
|
|
@ -397,8 +411,13 @@ func (h *SpaceHandler) CreateTag(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SpaceHandler) DeleteTag(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) DeleteTag(w http.ResponseWriter, r *http.Request) {
|
||||||
|
spaceID := r.PathValue("spaceID")
|
||||||
tagID := r.PathValue("tagID")
|
tagID := r.PathValue("tagID")
|
||||||
|
|
||||||
|
if h.getTagForSpace(w, spaceID, tagID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := h.tagService.DeleteTag(tagID)
|
err := h.tagService.DeleteTag(tagID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to delete tag", "error", err, "tag_id", tagID)
|
slog.Error("failed to delete tag", "error", err, "tag_id", tagID)
|
||||||
|
|
@ -817,6 +836,17 @@ func (h *SpaceHandler) CreateInvite(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
user := ctxkeys.User(r.Context())
|
user := ctxkeys.User(r.Context())
|
||||||
|
|
||||||
|
space, err := h.spaceService.GetSpace(spaceID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Space not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if space.OwnerID != user.ID {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
|
@ -828,7 +858,7 @@ func (h *SpaceHandler) CreateInvite(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := h.inviteService.CreateInvite(spaceID, user.ID, email)
|
_, err = h.inviteService.CreateInvite(spaceID, user.ID, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to create invite", "error", err, "space_id", spaceID)
|
slog.Error("failed to create invite", "error", err, "space_id", spaceID)
|
||||||
http.Error(w, "Failed to create invite", http.StatusInternalServerError)
|
http.Error(w, "Failed to create invite", http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -23,7 +24,12 @@ func CSRFProtection(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Skip CSRF check for safe methods (GET, HEAD, OPTIONS)
|
// Skip CSRF check for safe methods (GET, HEAD, OPTIONS)
|
||||||
if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
|
if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
|
||||||
token := getOrGenerateCSRFToken(w, r)
|
token, err := getOrGenerateCSRFToken(w, r)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to generate CSRF token", "error", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx := ctxkeys.WithCSRFToken(r.Context(), token)
|
ctx := ctxkeys.WithCSRFToken(r.Context(), token)
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
return
|
return
|
||||||
|
|
@ -36,7 +42,12 @@ func CSRFProtection(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate CSRF token for state-changing methods (POST, PUT, PATCH, DELETE)
|
// Validate CSRF token for state-changing methods (POST, PUT, PATCH, DELETE)
|
||||||
token := getOrGenerateCSRFToken(w, r)
|
token, err := getOrGenerateCSRFToken(w, r)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to generate CSRF token", "error", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx := ctxkeys.WithCSRFToken(r.Context(), token)
|
ctx := ctxkeys.WithCSRFToken(r.Context(), token)
|
||||||
|
|
||||||
// Get submitted token - try multiple sources in priority order
|
// Get submitted token - try multiple sources in priority order
|
||||||
|
|
@ -64,13 +75,16 @@ func CSRFProtection(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrGenerateCSRFToken retrieves existing token or generates new one
|
// getOrGenerateCSRFToken retrieves existing token or generates new one
|
||||||
func getOrGenerateCSRFToken(w http.ResponseWriter, r *http.Request) string {
|
func getOrGenerateCSRFToken(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||||
cookie, err := r.Cookie(csrfCookieName)
|
cookie, err := r.Cookie(csrfCookieName)
|
||||||
if err == nil && cookie.Value != "" && len(cookie.Value) == base64.RawURLEncoding.EncodedLen(csrfTokenLen) {
|
if err == nil && cookie.Value != "" && len(cookie.Value) == base64.RawURLEncoding.EncodedLen(csrfTokenLen) {
|
||||||
return cookie.Value
|
return cookie.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token := generateCSRFToken()
|
token, err := generateCSRFToken()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
cfg := ctxkeys.Config(r.Context())
|
cfg := ctxkeys.Config(r.Context())
|
||||||
isProduction := cfg != nil && cfg.IsProduction()
|
isProduction := cfg != nil && cfg.IsProduction()
|
||||||
|
|
@ -86,17 +100,17 @@ func getOrGenerateCSRFToken(w http.ResponseWriter, r *http.Request) string {
|
||||||
MaxAge: 86400 * 7, // 7 days
|
MaxAge: 86400 * 7, // 7 days
|
||||||
})
|
})
|
||||||
|
|
||||||
return token
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateCSRFToken creates cryptographically secure random token
|
// generateCSRFToken creates cryptographically secure random token
|
||||||
func generateCSRFToken() string {
|
func generateCSRFToken() (string, error) {
|
||||||
bytes := make([]byte, csrfTokenLen)
|
bytes := make([]byte, csrfTokenLen)
|
||||||
_, err := rand.Read(bytes)
|
_, err := rand.Read(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to generate csrf token: " + err.Error())
|
return "", fmt.Errorf("failed to generate csrf token: %w", err)
|
||||||
}
|
}
|
||||||
return base64.RawURLEncoding.EncodeToString(bytes)
|
return base64.RawURLEncoding.EncodeToString(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validCSRFToken performs constant-time comparison of tokens
|
// validCSRFToken performs constant-time comparison of tokens
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,29 @@ func RateLimitAuth() func(http.HandlerFunc) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RateLimitCRUD creates middleware for state-changing CRUD endpoints.
|
||||||
|
// Limits: 60 requests per minute per IP.
|
||||||
|
func RateLimitCRUD() func(http.Handler) http.Handler {
|
||||||
|
limiter := NewRateLimiter(60, 1*time.Minute)
|
||||||
|
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ip := getClientIP(r)
|
||||||
|
|
||||||
|
if !limiter.Allow(ip) {
|
||||||
|
slog.Warn("CRUD rate limit exceeded",
|
||||||
|
"ip", ip,
|
||||||
|
"path", r.URL.Path,
|
||||||
|
)
|
||||||
|
http.Error(w, "Too many requests. Please try again later.", http.StatusTooManyRequests)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getClientIP extracts real client IP from request
|
// getClientIP extracts real client IP from request
|
||||||
func getClientIP(r *http.Request) string {
|
func getClientIP(r *http.Request) string {
|
||||||
// Check X-Forwarded-For header (proxy/load balancer)
|
// Check X-Forwarded-For header (proxy/load balancer)
|
||||||
|
|
|
||||||
33
internal/middleware/security_headers.go
Normal file
33
internal/middleware/security_headers.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecurityHeaders sets common security response headers on every response.
|
||||||
|
// Note: HSTS is handled by Caddy at the reverse proxy layer.
|
||||||
|
func SecurityHeaders() func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h := w.Header()
|
||||||
|
|
||||||
|
h.Set("Content-Security-Policy",
|
||||||
|
"default-src 'self'; "+
|
||||||
|
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; "+
|
||||||
|
"style-src 'self' 'unsafe-inline'; "+
|
||||||
|
"img-src 'self' data:; "+
|
||||||
|
"connect-src 'self' https://www.google-analytics.com; "+
|
||||||
|
"font-src 'self'; "+
|
||||||
|
"frame-ancestors 'none'; "+
|
||||||
|
"base-uri 'self'; "+
|
||||||
|
"form-action 'self'")
|
||||||
|
|
||||||
|
h.Set("X-Frame-Options", "DENY")
|
||||||
|
h.Set("X-Content-Type-Options", "nosniff")
|
||||||
|
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||||
|
h.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()")
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
// Auth pages
|
// Auth pages
|
||||||
authRateLimiter := middleware.RateLimitAuth()
|
authRateLimiter := middleware.RateLimitAuth()
|
||||||
|
crudLimiter := middleware.RateLimitCRUD()
|
||||||
|
|
||||||
mux.HandleFunc("GET /auth", middleware.RequireGuest(auth.AuthPage))
|
mux.HandleFunc("GET /auth", middleware.RequireGuest(auth.AuthPage))
|
||||||
mux.HandleFunc("GET /auth/password", middleware.RequireGuest(auth.PasswordPage))
|
mux.HandleFunc("GET /auth/password", middleware.RequireGuest(auth.PasswordPage))
|
||||||
|
|
@ -52,10 +53,10 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
// ====================================================================================
|
// ====================================================================================
|
||||||
|
|
||||||
mux.HandleFunc("GET /auth/onboarding", middleware.RequireAuth(auth.OnboardingPage))
|
mux.HandleFunc("GET /auth/onboarding", middleware.RequireAuth(auth.OnboardingPage))
|
||||||
mux.HandleFunc("POST /auth/onboarding", middleware.RequireAuth(auth.CompleteOnboarding))
|
mux.Handle("POST /auth/onboarding", crudLimiter(http.HandlerFunc(middleware.RequireAuth(auth.CompleteOnboarding))))
|
||||||
|
|
||||||
mux.HandleFunc("GET /app/dashboard", middleware.RequireAuth(dashboard.DashboardPage))
|
mux.HandleFunc("GET /app/dashboard", middleware.RequireAuth(dashboard.DashboardPage))
|
||||||
mux.HandleFunc("POST /app/spaces", middleware.RequireAuth(dashboard.CreateSpace))
|
mux.Handle("POST /app/spaces", crudLimiter(http.HandlerFunc(middleware.RequireAuth(dashboard.CreateSpace))))
|
||||||
mux.HandleFunc("GET /app/settings", middleware.RequireAuth(settings.SettingsPage))
|
mux.HandleFunc("GET /app/settings", middleware.RequireAuth(settings.SettingsPage))
|
||||||
mux.HandleFunc("POST /app/settings/password", authRateLimiter(middleware.RequireAuth(settings.SetPassword)))
|
mux.HandleFunc("POST /app/settings/password", authRateLimiter(middleware.RequireAuth(settings.SetPassword)))
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createListHandler := middleware.RequireAuth(space.CreateList)
|
createListHandler := middleware.RequireAuth(space.CreateList)
|
||||||
createListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createListHandler)
|
createListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createListHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/lists", createListWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/lists", crudLimiter(createListWithAccess))
|
||||||
|
|
||||||
listPageHandler := middleware.RequireAuth(space.ListPage)
|
listPageHandler := middleware.RequireAuth(space.ListPage)
|
||||||
listPageWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(listPageHandler)
|
listPageWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(listPageHandler)
|
||||||
|
|
@ -78,23 +79,23 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
updateListHandler := middleware.RequireAuth(space.UpdateList)
|
updateListHandler := middleware.RequireAuth(space.UpdateList)
|
||||||
updateListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateListHandler)
|
updateListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateListHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}", updateListWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}", crudLimiter(updateListWithAccess))
|
||||||
|
|
||||||
deleteListHandler := middleware.RequireAuth(space.DeleteList)
|
deleteListHandler := middleware.RequireAuth(space.DeleteList)
|
||||||
deleteListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteListHandler)
|
deleteListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteListHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/lists/{listID}", deleteListWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/lists/{listID}", crudLimiter(deleteListWithAccess))
|
||||||
|
|
||||||
addItemHandler := middleware.RequireAuth(space.AddItemToList)
|
addItemHandler := middleware.RequireAuth(space.AddItemToList)
|
||||||
addItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(addItemHandler)
|
addItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(addItemHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/lists/{listID}/items", addItemWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/lists/{listID}/items", crudLimiter(addItemWithAccess))
|
||||||
|
|
||||||
toggleItemHandler := middleware.RequireAuth(space.ToggleItem)
|
toggleItemHandler := middleware.RequireAuth(space.ToggleItem)
|
||||||
toggleItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(toggleItemHandler)
|
toggleItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(toggleItemHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}/items/{itemID}", toggleItemWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}/items/{itemID}", crudLimiter(toggleItemWithAccess))
|
||||||
|
|
||||||
deleteItemHandler := middleware.RequireAuth(space.DeleteItem)
|
deleteItemHandler := middleware.RequireAuth(space.DeleteItem)
|
||||||
deleteItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteItemHandler)
|
deleteItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteItemHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/lists/{listID}/items/{itemID}", deleteItemWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/lists/{listID}/items/{itemID}", crudLimiter(deleteItemWithAccess))
|
||||||
|
|
||||||
// Tag routes
|
// Tag routes
|
||||||
tagsPageHandler := middleware.RequireAuth(space.TagsPage)
|
tagsPageHandler := middleware.RequireAuth(space.TagsPage)
|
||||||
|
|
@ -103,11 +104,11 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createTagHandler := middleware.RequireAuth(space.CreateTag)
|
createTagHandler := middleware.RequireAuth(space.CreateTag)
|
||||||
createTagWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createTagHandler)
|
createTagWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createTagHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/tags", createTagWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/tags", crudLimiter(createTagWithAccess))
|
||||||
|
|
||||||
deleteTagHandler := middleware.RequireAuth(space.DeleteTag)
|
deleteTagHandler := middleware.RequireAuth(space.DeleteTag)
|
||||||
deleteTagWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteTagHandler)
|
deleteTagWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteTagHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/tags/{tagID}", deleteTagWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/tags/{tagID}", crudLimiter(deleteTagWithAccess))
|
||||||
|
|
||||||
// Expense routes
|
// Expense routes
|
||||||
expensesPageHandler := middleware.RequireAuth(space.ExpensesPage)
|
expensesPageHandler := middleware.RequireAuth(space.ExpensesPage)
|
||||||
|
|
@ -116,15 +117,15 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createExpenseHandler := middleware.RequireAuth(space.CreateExpense)
|
createExpenseHandler := middleware.RequireAuth(space.CreateExpense)
|
||||||
createExpenseWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createExpenseHandler)
|
createExpenseWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createExpenseHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/expenses", createExpenseWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/expenses", crudLimiter(createExpenseWithAccess))
|
||||||
|
|
||||||
updateExpenseHandler := middleware.RequireAuth(space.UpdateExpense)
|
updateExpenseHandler := middleware.RequireAuth(space.UpdateExpense)
|
||||||
updateExpenseWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateExpenseHandler)
|
updateExpenseWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateExpenseHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/expenses/{expenseID}", updateExpenseWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/expenses/{expenseID}", crudLimiter(updateExpenseWithAccess))
|
||||||
|
|
||||||
deleteExpenseHandler := middleware.RequireAuth(space.DeleteExpense)
|
deleteExpenseHandler := middleware.RequireAuth(space.DeleteExpense)
|
||||||
deleteExpenseWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteExpenseHandler)
|
deleteExpenseWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteExpenseHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/expenses/{expenseID}", deleteExpenseWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/expenses/{expenseID}", crudLimiter(deleteExpenseWithAccess))
|
||||||
|
|
||||||
// Money Account routes
|
// Money Account routes
|
||||||
accountsPageHandler := middleware.RequireAuth(space.AccountsPage)
|
accountsPageHandler := middleware.RequireAuth(space.AccountsPage)
|
||||||
|
|
@ -133,23 +134,23 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createAccountHandler := middleware.RequireAuth(space.CreateAccount)
|
createAccountHandler := middleware.RequireAuth(space.CreateAccount)
|
||||||
createAccountWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createAccountHandler)
|
createAccountWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createAccountHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/accounts", createAccountWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/accounts", crudLimiter(createAccountWithAccess))
|
||||||
|
|
||||||
updateAccountHandler := middleware.RequireAuth(space.UpdateAccount)
|
updateAccountHandler := middleware.RequireAuth(space.UpdateAccount)
|
||||||
updateAccountWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateAccountHandler)
|
updateAccountWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateAccountHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/accounts/{accountID}", updateAccountWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/accounts/{accountID}", crudLimiter(updateAccountWithAccess))
|
||||||
|
|
||||||
deleteAccountHandler := middleware.RequireAuth(space.DeleteAccount)
|
deleteAccountHandler := middleware.RequireAuth(space.DeleteAccount)
|
||||||
deleteAccountWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteAccountHandler)
|
deleteAccountWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteAccountHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/accounts/{accountID}", deleteAccountWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/accounts/{accountID}", crudLimiter(deleteAccountWithAccess))
|
||||||
|
|
||||||
createTransferHandler := middleware.RequireAuth(space.CreateTransfer)
|
createTransferHandler := middleware.RequireAuth(space.CreateTransfer)
|
||||||
createTransferWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createTransferHandler)
|
createTransferWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createTransferHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/accounts/{accountID}/transfers", createTransferWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/accounts/{accountID}/transfers", crudLimiter(createTransferWithAccess))
|
||||||
|
|
||||||
deleteTransferHandler := middleware.RequireAuth(space.DeleteTransfer)
|
deleteTransferHandler := middleware.RequireAuth(space.DeleteTransfer)
|
||||||
deleteTransferWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteTransferHandler)
|
deleteTransferWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteTransferHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/accounts/{accountID}/transfers/{transferID}", deleteTransferWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/accounts/{accountID}/transfers/{transferID}", crudLimiter(deleteTransferWithAccess))
|
||||||
|
|
||||||
// Payment Method routes
|
// Payment Method routes
|
||||||
methodsPageHandler := middleware.RequireAuth(space.PaymentMethodsPage)
|
methodsPageHandler := middleware.RequireAuth(space.PaymentMethodsPage)
|
||||||
|
|
@ -158,15 +159,15 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createMethodHandler := middleware.RequireAuth(space.CreatePaymentMethod)
|
createMethodHandler := middleware.RequireAuth(space.CreatePaymentMethod)
|
||||||
createMethodWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createMethodHandler)
|
createMethodWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createMethodHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/payment-methods", createMethodWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/payment-methods", crudLimiter(createMethodWithAccess))
|
||||||
|
|
||||||
updateMethodHandler := middleware.RequireAuth(space.UpdatePaymentMethod)
|
updateMethodHandler := middleware.RequireAuth(space.UpdatePaymentMethod)
|
||||||
updateMethodWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateMethodHandler)
|
updateMethodWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateMethodHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/payment-methods/{methodID}", updateMethodWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/payment-methods/{methodID}", crudLimiter(updateMethodWithAccess))
|
||||||
|
|
||||||
deleteMethodHandler := middleware.RequireAuth(space.DeletePaymentMethod)
|
deleteMethodHandler := middleware.RequireAuth(space.DeletePaymentMethod)
|
||||||
deleteMethodWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteMethodHandler)
|
deleteMethodWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteMethodHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/payment-methods/{methodID}", deleteMethodWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/payment-methods/{methodID}", crudLimiter(deleteMethodWithAccess))
|
||||||
|
|
||||||
// Recurring expense routes
|
// Recurring expense routes
|
||||||
recurringPageHandler := middleware.RequireAuth(space.RecurringExpensesPage)
|
recurringPageHandler := middleware.RequireAuth(space.RecurringExpensesPage)
|
||||||
|
|
@ -175,19 +176,19 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createRecurringHandler := middleware.RequireAuth(space.CreateRecurringExpense)
|
createRecurringHandler := middleware.RequireAuth(space.CreateRecurringExpense)
|
||||||
createRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createRecurringHandler)
|
createRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createRecurringHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/recurring", createRecurringWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/recurring", crudLimiter(createRecurringWithAccess))
|
||||||
|
|
||||||
updateRecurringHandler := middleware.RequireAuth(space.UpdateRecurringExpense)
|
updateRecurringHandler := middleware.RequireAuth(space.UpdateRecurringExpense)
|
||||||
updateRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateRecurringHandler)
|
updateRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateRecurringHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/recurring/{recurringID}", updateRecurringWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/recurring/{recurringID}", crudLimiter(updateRecurringWithAccess))
|
||||||
|
|
||||||
deleteRecurringHandler := middleware.RequireAuth(space.DeleteRecurringExpense)
|
deleteRecurringHandler := middleware.RequireAuth(space.DeleteRecurringExpense)
|
||||||
deleteRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteRecurringHandler)
|
deleteRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteRecurringHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/recurring/{recurringID}", deleteRecurringWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/recurring/{recurringID}", crudLimiter(deleteRecurringWithAccess))
|
||||||
|
|
||||||
toggleRecurringHandler := middleware.RequireAuth(space.ToggleRecurringExpense)
|
toggleRecurringHandler := middleware.RequireAuth(space.ToggleRecurringExpense)
|
||||||
toggleRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(toggleRecurringHandler)
|
toggleRecurringWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(toggleRecurringHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/recurring/{recurringID}/toggle", toggleRecurringWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/recurring/{recurringID}/toggle", crudLimiter(toggleRecurringWithAccess))
|
||||||
|
|
||||||
// Budget routes
|
// Budget routes
|
||||||
budgetsPageHandler := middleware.RequireAuth(space.BudgetsPage)
|
budgetsPageHandler := middleware.RequireAuth(space.BudgetsPage)
|
||||||
|
|
@ -196,15 +197,15 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
createBudgetHandler := middleware.RequireAuth(space.CreateBudget)
|
createBudgetHandler := middleware.RequireAuth(space.CreateBudget)
|
||||||
createBudgetWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createBudgetHandler)
|
createBudgetWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createBudgetHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/budgets", createBudgetWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/budgets", crudLimiter(createBudgetWithAccess))
|
||||||
|
|
||||||
updateBudgetHandler := middleware.RequireAuth(space.UpdateBudget)
|
updateBudgetHandler := middleware.RequireAuth(space.UpdateBudget)
|
||||||
updateBudgetWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateBudgetHandler)
|
updateBudgetWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateBudgetHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/budgets/{budgetID}", updateBudgetWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/budgets/{budgetID}", crudLimiter(updateBudgetWithAccess))
|
||||||
|
|
||||||
deleteBudgetHandler := middleware.RequireAuth(space.DeleteBudget)
|
deleteBudgetHandler := middleware.RequireAuth(space.DeleteBudget)
|
||||||
deleteBudgetWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteBudgetHandler)
|
deleteBudgetWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteBudgetHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/budgets/{budgetID}", deleteBudgetWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/budgets/{budgetID}", crudLimiter(deleteBudgetWithAccess))
|
||||||
|
|
||||||
budgetsListHandler := middleware.RequireAuth(space.GetBudgetsList)
|
budgetsListHandler := middleware.RequireAuth(space.GetBudgetsList)
|
||||||
budgetsListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(budgetsListHandler)
|
budgetsListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(budgetsListHandler)
|
||||||
|
|
@ -247,15 +248,15 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
|
|
||||||
updateSpaceNameHandler := middleware.RequireAuth(space.UpdateSpaceName)
|
updateSpaceNameHandler := middleware.RequireAuth(space.UpdateSpaceName)
|
||||||
updateSpaceNameWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateSpaceNameHandler)
|
updateSpaceNameWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateSpaceNameHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/settings/name", updateSpaceNameWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/settings/name", crudLimiter(updateSpaceNameWithAccess))
|
||||||
|
|
||||||
removeMemberHandler := middleware.RequireAuth(space.RemoveMember)
|
removeMemberHandler := middleware.RequireAuth(space.RemoveMember)
|
||||||
removeMemberWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(removeMemberHandler)
|
removeMemberWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(removeMemberHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/members/{userID}", removeMemberWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/members/{userID}", crudLimiter(removeMemberWithAccess))
|
||||||
|
|
||||||
cancelInviteHandler := middleware.RequireAuth(space.CancelInvite)
|
cancelInviteHandler := middleware.RequireAuth(space.CancelInvite)
|
||||||
cancelInviteWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(cancelInviteHandler)
|
cancelInviteWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(cancelInviteHandler)
|
||||||
mux.Handle("DELETE /app/spaces/{spaceID}/invites/{token}", cancelInviteWithAccess)
|
mux.Handle("DELETE /app/spaces/{spaceID}/invites/{token}", crudLimiter(cancelInviteWithAccess))
|
||||||
|
|
||||||
getPendingInvitesHandler := middleware.RequireAuth(space.GetPendingInvites)
|
getPendingInvitesHandler := middleware.RequireAuth(space.GetPendingInvites)
|
||||||
getPendingInvitesWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(getPendingInvitesHandler)
|
getPendingInvitesWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(getPendingInvitesHandler)
|
||||||
|
|
@ -264,7 +265,7 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
// Invite routes
|
// Invite routes
|
||||||
createInviteHandler := middleware.RequireAuth(space.CreateInvite)
|
createInviteHandler := middleware.RequireAuth(space.CreateInvite)
|
||||||
createInviteWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createInviteHandler)
|
createInviteWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(createInviteHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/invites", createInviteWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/invites", crudLimiter(createInviteWithAccess))
|
||||||
|
|
||||||
mux.HandleFunc("GET /join/{token}", space.JoinSpace)
|
mux.HandleFunc("GET /join/{token}", space.JoinSpace)
|
||||||
|
|
||||||
|
|
@ -274,6 +275,7 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
// Global middlewares
|
// Global middlewares
|
||||||
handler := middleware.Chain(
|
handler := middleware.Chain(
|
||||||
mux,
|
mux,
|
||||||
|
middleware.SecurityHeaders(),
|
||||||
middleware.AppVersion(a.Cfg.Version),
|
middleware.AppVersion(a.Cfg.Version),
|
||||||
middleware.Config(a.Cfg),
|
middleware.Config(a.Cfg),
|
||||||
middleware.RequestLogging,
|
middleware.RequestLogging,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue