Files
kandev/apps/backend/internal/authz/gin.go
T
João Salavisa 481359ddb2 feat(auth): organization units, tenancy, roles and scopes for team access (#2919)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 13:40:43 +00:00

49 lines
1.4 KiB
Go

package authz
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/kandev/kandev/internal/auth/authn"
)
// SubjectFromGin builds the authz subject for a gin request. A request with no
// identity, or the synthetic identity injected while authentication is
// disabled, is unscoped and holds every scope — preserving pre-auth behavior
// byte for byte.
func SubjectFromGin(c *gin.Context) Subject {
identity, ok := authn.FromGin(c)
if !ok || identity.Synthetic {
return Subject{Unscoped: true}
}
return Subject{
UserID: identity.UserID,
OrgID: identity.OrgID,
OrgRole: NormalizeOrgRole(string(identity.Role)),
}
}
// RequireOrgScope aborts with 403 unless the caller holds the org scope.
// Requests with no identity at all are rejected with 401.
//
// This replaces the previous per-route admin bit. Naming the capability rather
// than the role is the point: "which routes need org.config.manage" is a
// question the registry can answer, "which routes called RequireAdmin" was not.
func RequireOrgScope(scope Scope) gin.HandlerFunc {
return func(c *gin.Context) {
if _, ok := authn.FromGin(c); !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
return
}
if !SubjectOrgScopes(SubjectFromGin(c)).Has(scope) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "insufficient permissions",
"scope": string(scope),
})
return
}
c.Next()
}
}