feat(api): RequireAuth+RequireProjectAccess middleware, IDOR-scope check/apply по projectID

This commit is contained in:
2026-07-03 20:47:40 +07:00
parent 35ffe73ae3
commit 4533b0ca25
16 changed files with 498 additions and 143 deletions
+14 -4
View File
@@ -18,8 +18,8 @@ import (
// CheckApplier is the service surface the API depends on.
type CheckApplier interface {
Check(ctx context.Context, domainID uuid.UUID) (diff.Changeset, error)
Apply(ctx context.Context, domainID uuid.UUID, req service.ApplyRequest) (diff.Changeset, error)
Check(ctx context.Context, projectID, domainID uuid.UUID) (diff.Changeset, error)
Apply(ctx context.Context, projectID, domainID uuid.UUID, req service.ApplyRequest) (diff.Changeset, error)
}
// TenantStore is the narrow persistence surface the CRUD handlers depend on.
@@ -63,6 +63,10 @@ type AuthStore interface {
GetUserByEmail(ctx context.Context, email string) (store.User, error)
GetUserByID(ctx context.Context, userID uuid.UUID) (store.User, error)
GetUserProject(ctx context.Context, userID uuid.UUID) (store.Project, error)
// GetProjectOwned looks up projectID and returns it only if it's owned by
// userID — RequireProjectAccess uses this to reject foreign/nonexistent
// projects with 404 before any handler runs.
GetProjectOwned(ctx context.Context, projectID, userID uuid.UUID) (store.Project, error)
}
// SessionManager creates/validates/destroys login sessions. *auth.Sessions
@@ -91,11 +95,17 @@ func NewRouter(a *API) http.Handler {
r.Route("/api/v1/auth", func(r chi.Router) {
r.Post("/register", a.handleRegister)
r.Post("/login", a.handleLogin)
r.Post("/logout", a.handleLogout) // защитится RequireAuth в Task 4
r.Get("/me", a.handleMe) // защитится RequireAuth в Task 4
r.Group(func(r chi.Router) {
r.Use(a.RequireAuth)
r.Post("/logout", a.handleLogout)
r.Get("/me", a.handleMe)
})
})
r.Route("/api/v1/projects/{pid}", func(r chi.Router) {
r.Use(a.RequireAuth)
r.Use(a.RequireProjectAccess)
r.Route("/domains", func(r chi.Router) {
r.Post("/", a.handleCreateDomain)
r.Get("/", a.handleListDomains)