diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 28fb664..e13a36a 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -206,7 +206,10 @@ func TokenInfoFromContext(ctx context.Context) *TokenInfo { } // AuthInterceptor returns a gRPC unary server interceptor that validates -// bearer tokens and requires the "admin" role. +// bearer tokens. Any authenticated user or system account is accepted, +// except guests which are explicitly rejected. Admin role is not required +// for agent operations — it is reserved for MCIAS account management and +// policy changes. func AuthInterceptor(validator TokenValidator) grpc.UnaryServerInterceptor { return func( ctx context.Context, @@ -240,9 +243,9 @@ func AuthInterceptor(validator TokenValidator) grpc.UnaryServerInterceptor { return nil, status.Error(codes.Unauthenticated, "invalid token") } - if !tokenInfo.HasRole("admin") { - slog.Warn("permission denied", "method", info.FullMethod, "user", tokenInfo.Username) - return nil, status.Error(codes.PermissionDenied, "admin role required") + if tokenInfo.HasRole("guest") { + slog.Warn("guest access denied", "method", info.FullMethod, "user", tokenInfo.Username) + return nil, status.Error(codes.PermissionDenied, "guest access not permitted") } slog.Info("rpc", "method", info.FullMethod, "user", tokenInfo.Username, "account_type", tokenInfo.AccountType) diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index 803a614..fe6703a 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -126,7 +126,7 @@ func TestInterceptorRejectsInvalidToken(t *testing.T) { } } -func TestInterceptorRejectsNonAdmin(t *testing.T) { +func TestInterceptorAcceptsRegularUser(t *testing.T) { server := mockMCIAS(t, func(authHeader string) (any, int) { return &TokenInfo{ Valid: true, @@ -142,6 +142,28 @@ func TestInterceptorRejectsNonAdmin(t *testing.T) { md := metadata.Pairs("authorization", "Bearer user-token") ctx := metadata.NewIncomingContext(context.Background(), md) + _, err := callInterceptor(ctx, v) + if err != nil { + t.Fatalf("expected regular user to be accepted, got %v", err) + } +} + +func TestInterceptorRejectsGuest(t *testing.T) { + server := mockMCIAS(t, func(authHeader string) (any, int) { + return &TokenInfo{ + Valid: true, + Username: "visitor", + Roles: []string{"guest"}, + AccountType: "human", + }, http.StatusOK + }) + defer server.Close() + + v := validatorFromServer(t, server) + + md := metadata.Pairs("authorization", "Bearer guest-token") + ctx := metadata.NewIncomingContext(context.Background(), md) + _, err := callInterceptor(ctx, v) if err == nil { t.Fatal("expected error, got nil")