feat(auth): RequireNotRestricted middleware
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e0ea7f0bde
commit
700567071b
2 changed files with 77 additions and 0 deletions
|
|
@ -55,6 +55,20 @@ func RequireAdmin(next http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
// RequireNotRestricted is middleware that blocks users with role "restricted".
|
||||
// It must be chained after RequireAuth (so a user is present in context).
|
||||
// On failure it responds with 403 Forbidden.
|
||||
func RequireNotRestricted(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := UserFromContext(r.Context())
|
||||
if user != nil && user.Role == "restricted" {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// RequireTenantAccess is middleware that allows access only when the
|
||||
// authenticated user belongs to the tenant identified by the {tenantSlug}
|
||||
// path value, or when the user has role "admin" (admins can access everything).
|
||||
|
|
|
|||
63
server/backend/internal/httpapi/middleware_test.go
Normal file
63
server/backend/internal/httpapi/middleware_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package httpapi_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/httpapi"
|
||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/reqcontext"
|
||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
|
||||
)
|
||||
|
||||
func userCtx(role string) context.Context {
|
||||
return reqcontext.WithUser(context.Background(), &store.User{Role: role})
|
||||
}
|
||||
|
||||
func TestRequireNotRestricted_blocks_restricted(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil).WithContext(userCtx("restricted"))
|
||||
rr := httptest.NewRecorder()
|
||||
httpapi.RequireNotRestricted(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("should not be called")
|
||||
})).ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusForbidden {
|
||||
t.Fatalf("expected 403, got %d", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireNotRestricted_allows_screen_user(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil).WithContext(userCtx("screen_user"))
|
||||
rr := httptest.NewRecorder()
|
||||
called := false
|
||||
httpapi.RequireNotRestricted(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
})).ServeHTTP(rr, req)
|
||||
if !called {
|
||||
t.Fatal("expected next to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireNotRestricted_allows_admin(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil).WithContext(userCtx("admin"))
|
||||
rr := httptest.NewRecorder()
|
||||
called := false
|
||||
httpapi.RequireNotRestricted(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
})).ServeHTTP(rr, req)
|
||||
if !called {
|
||||
t.Fatal("expected next to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireNotRestricted_allows_no_user(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
called := false
|
||||
httpapi.RequireNotRestricted(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
})).ServeHTTP(rr, req)
|
||||
if !called {
|
||||
t.Fatal("no user in context — RequireAuth handles that, this middleware passes through")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue