feat: add namespace blocking functionality for JWT authentication (#313)

## Summary

Implements a denylist mechanism to prevent blocked namespaces from
publishing packages:

- Added `BlockedNamespaces` field to `JWTManager` struct for maintaining
the denylist
- Added validation logic in `GenerateTokenResponse` that checks user
permissions against blocked namespaces
- Admins with global permissions (`*`) bypass the blocking logic
- Added comprehensive test coverage for all blocking scenarios

Fixes #98
This commit is contained in:
adam jones 2025-08-27 23:46:35 +01:00 • committed by GitHub
commit 9d04671ee3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -72,6 +72,24 @@ func NewJWTManager(cfg *config.Config) *JWTManager {
// GenerateToken generates a new Registry JWT token
func (j *JWTManager) GenerateTokenResponse(_ context.Context, claims JWTClaims) (*TokenResponse, error) {
// Check whether they have global permissions (used by admins)
hasGlobalPermissions := false
for _, perm := range claims.Permissions {
if perm.ResourcePattern == "*" {
hasGlobalPermissions = true
break
}
}
// Check permissions against denylist, provided they are not an admin
if !hasGlobalPermissions {
for _, blockedNamespace := range BlockedNamespaces {
if j.HasPermission(blockedNamespace+"/test", PermissionActionPublish, claims.Permissions) {
return nil, fmt.Errorf("your namespace is blocked. raise an issue at https://github.com/modelcontextprotocol/registry/ if you think this is a mistake")
}
}
}
if claims.IssuedAt == nil {
claims.IssuedAt = jwt.NewNumericDate(time.Now())
}