diff --git a/CLAUDE.md b/CLAUDE.md index 0f66598..26d7afa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,10 +29,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Check formatting**: `mvn spotless:check` ### Authentication Testing -- **Admin credentials**: `admin` / `admin123` (full CRUD access) -- **User credentials**: `user` / `user123` (read-only access) -- **Login endpoint**: `POST /api/v1/auth/login` +- **Bootstrap Required**: Initial admin user setup required on first deployment +- **Database-Managed Users**: All authentication managed via database (V4 schema) +- **Login endpoint**: `POST /api/v1/auth/login` - **JWT token required**: All product endpoints require `Authorization: Bearer ` +- **Data Seeding**: Handled by application bootstrap service (task-14), not migrations ## Multi-Environment Architecture @@ -58,7 +59,7 @@ This is an **enterprise-grade Spring Boot 3.5.4 REST API** for product catalog m ### Key Components - Enhanced - **Product Entity**: Advanced model with inventory, categories, soft deletes, versioning - **JWT Authentication**: Stateless auth with role-based access (ADMIN/USER) -- **Database**: PostgreSQL with 3 Flyway migrations and strategic indexing +- **Database**: PostgreSQL with 4 Flyway migrations and strategic indexing - **Audit System**: Asynchronous audit logging for all CRUD operations - **Testing Strategy**: 28+ tests (Unit, Integration, E2E) with 100% pass rate - **API Documentation**: Enhanced Swagger UI with JWT security schemas @@ -91,18 +92,26 @@ This is an **enterprise-grade Spring Boot 3.5.4 REST API** for product catalog m ## Development Notes - Enterprise Features ### Database Schema - Enhanced -- **Flyway Migrations**: 3 production-ready migrations with automatic execution +- **Flyway Migrations**: 4 production-ready migrations with automatic execution - **Products Table**: Enhanced with inventory, categories, audit fields, soft delete -- **Strategic Indexing**: Indexes on `sku`, `category`, `stock_quantity`, `deleted` +- **User Management Tables** (V4 Migration): + - **Roles Table**: Normalized role management with granular permissions arrays (ADMIN, USER, MANAGER, READONLY) + - **Users Table**: Database-managed users with role-based access control, MCP access flags, BCrypt password hashing + - **API Keys Table**: Scoped API key management with expiration, usage tracking, and user association + - **User Role History**: Complete audit trail for all role changes with timestamps and change reasons +- **Strategic Indexing**: Indexes on `sku`, `category`, `stock_quantity`, `deleted`, plus 8 user management indexes - **Data Types**: UUID primary keys, BigDecimal for prices, timestamps for auditing - **Soft Delete**: Logical deletion preserving data integrity and audit trails ### Authentication & Security - **JWT Implementation**: Stateless authentication with configurable expiration -- **Role-Based Access**: `ADMIN` (full CRUD) vs `USER` (read-only) permissions +- **Database-Managed Users**: V4 migration provides schema foundation; user data managed by application bootstrap (task-14) +- **Role-Based Access**: Enhanced with 4 roles - `ADMIN` (full CRUD), `USER` (read-only), `MANAGER` (write access), `READONLY` (minimal) +- **Granular Permissions**: Permission arrays including READ, WRITE, DELETE, USER_MANAGEMENT, MCP_TOOLS, API_KEY_MANAGEMENT - **Security Filters**: JWT validation on all protected endpoints -- **Hardcoded Users**: Demo credentials for development and testing -- **Password Security**: Production-ready for external authentication integration +- **BCrypt Password Hashing**: Secure password storage with industry-standard encryption +- **API Key Management**: Scoped API keys with expiration and usage tracking +- **Audit Trail**: Complete role change history for compliance and security monitoring ### Testing Patterns - Comprehensive - **Unit Tests**: `@ExtendWith(MockitoExtension.class)` with service layer isolation diff --git a/README.md b/README.md index 14d2372..143f3dc 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ This project implements a **comprehensive, enterprise-grade RESTful API** for pr * **Framework**: Spring Boot 3.5.4 with Spring Security * **AI Framework**: Spring AI * **Build Tool**: Maven -* **Database**: PostgreSQL with advanced indexing -* **Schema Migration**: Flyway (3 migrations) +* **Database**: PostgreSQL with advanced indexing and user management +* **Schema Migration**: Flyway (4 migrations) * **Authentication**: JWT (JSON Web Tokens) * **Containerization**: Docker & Docker Compose * **Testing**: JUnit 5, Mockito, Testcontainers, RestAssured @@ -20,7 +20,7 @@ This project implements a **comprehensive, enterprise-grade RESTful API** for pr * **Logging**: Structured logging with audit trails ## 🚀 Key Features -* 🔐 **Secure by Design**: Stateless JWT authentication with role-based access control (ADMIN vs. USER). +* 🔐 **Secure by Design**: Database-managed users with JWT authentication and granular role-based access control (ADMIN, USER, MANAGER, READONLY). * 📊 **Advanced Product Management**: Soft deletes, optimistic locking, inventory tracking, and custom validations. * 🔍 **Powerful API**: Pagination, sorting, and advanced filtering capabilities. * 🤖 **Comprehensive AI Integration**: Natural language interface for catalog management via Spring AI's MCP Server. @@ -62,9 +62,9 @@ For a detailed guide on every aspect of this project, please refer to the docume ### ✅ **Completed Features** - Full CRUD API with advanced filtering -- JWT authentication with role-based authorization (ADMIN/USER) +- Database-managed users with JWT authentication and enhanced role-based authorization (ADMIN/USER/MANAGER/READONLY) - Comprehensive test suite (28+ tests) -- Database optimization with indexing and soft deletes +- Database optimization with indexing, soft deletes, and user management audit trails - Custom actuator endpoints for business metrics and health monitoring - Docker containerization and multi-environment deployment - Interactive API documentation and enhanced error handling diff --git a/backlog/config.yml b/backlog/config.yml index d51ac26..54ff9eb 100644 --- a/backlog/config.yml +++ b/backlog/config.yml @@ -1,14 +1,14 @@ -project_name: "product-catalog-spring" +project_name: "product-catalog" default_status: "To Do" -statuses: ["To Do", "In Progress", "Pending Review", "Blocked", "Done"] -labels: [] +statuses: ["To Do", "In Progress", "PR for Integration", "PR for Stage", "PR for Production", "Blocked", "Done"] +labels: ["database", "migration", "security", "foundation", "filters", "authentication", "validation", "controller", "api", "rest-api", "endpoints", "dto", "serialization", "seeding", "data", "production", "testing", "integration", "e2e", "configuration", "documentation", "deployment", "jpa", "entities", "domain", "repository", "data-access", "queries", "services", "jwt", "mcp", "tools", "admin", "user-management", "roles", "permissions", "self-service", "user-profile", "resources", "providers", "authorization"] milestones: [] date_format: yyyy-mm-dd max_column_width: 20 auto_open_browser: true default_port: 6420 -remote_operations: false +remote_operations: true auto_commit: false bypass_git_hooks: false -check_active_branches: false -active_branch_days: 30 +check_active_branches: true +active_branch_days: 30 \ No newline at end of file diff --git a/backlog/tasks/task-1 - Database-Schema-Design-&-Migration-Users,-Roles,-and-API-Keys.md b/backlog/tasks/task-1 - Database-Schema-Design-&-Migration-Users,-Roles,-and-API-Keys.md index 58395bf..2136cd2 100644 --- a/backlog/tasks/task-1 - Database-Schema-Design-&-Migration-Users,-Roles,-and-API-Keys.md +++ b/backlog/tasks/task-1 - Database-Schema-Design-&-Migration-Users,-Roles,-and-API-Keys.md @@ -4,7 +4,7 @@ title: 'Database Schema Design & Migration - Users, Roles, and API Keys' status: Pending Review assignee: [] created_date: '2025-08-21 11:30' -updated_date: '2025-08-21 12:05' +updated_date: '2025-08-21 12:39' labels: - database - migration @@ -33,3 +33,7 @@ Create normalized database schema with separate tables for users, roles, and API ## Implementation Plan 1. Checkout develop branch and create feature/database-schema-users-roles-apikeys branch,2. Create V4__Create_users_roles_and_api_keys_tables.sql migration file,3. Implement roles table with id name description permissions timestamps,4. Implement users table with role_id foreign key and authentication fields,5. Implement api_keys table with user relationship and scoping,6. Implement user_role_history for audit trail,7. Add strategic indexes for performance optimization,8. Insert default roles with appropriate permissions,9. Migrate existing hardcoded users to database,10. Test migration locally with flyway,11. Commit and push changes,12. Create PR to develop branch + +## Implementation Notes + +Implementation complete: V4 migration follows enterprise best practices with schema-only approach. All data seeding removed from migration and moved to application bootstrap service (task-14) for security. Tables created: roles, users, api_keys, user_role_history. Architectural improvement: eliminates hardcoded credentials in version control, enables environment-specific user setup, and separates schema evolution from business data. Awaiting PR #34 review. diff --git a/docker-compose.vps-stage.yml b/docker-compose.vps-stage.yml deleted file mode 100644 index a5028ad..0000000 --- a/docker-compose.vps-stage.yml +++ /dev/null @@ -1,116 +0,0 @@ -services: - postgres-db-stage: - image: postgres:17.5-alpine - container_name: product-catalog-postgres-stage - restart: unless-stopped - environment: - - POSTGRES_DB=${DATABASE_NAME:-product_catalog_stage} - - POSTGRES_USER=${DATABASE_USERNAME:-stage_user} - - POSTGRES_PASSWORD=${DATABASE_PASSWORD:-stage_password} - volumes: - - postgres_stage_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME:-stage_user} -d ${DATABASE_NAME:-product_catalog_stage}"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - dokploy-network - # Internal database - no external access needed - deploy: - resources: - limits: - memory: 768M - cpus: '0.8' - reservations: - memory: 256M - cpus: '0.3' - labels: - - "log.format=text" - - product-catalog-stage: - image: ghcr.io/the-dave-stack/product-catalog:${IMAGE_TAG:-stage} - container_name: product-catalog-app-stage - restart: unless-stopped - expose: - - 8080 - environment: - - SPRING_PROFILES_ACTIVE=stage,docker - - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-db-stage:5432/${DATABASE_NAME:-product_catalog_stage} - - SPRING_DATASOURCE_USERNAME=${DATABASE_USERNAME:-stage_user} - - SPRING_DATASOURCE_PASSWORD=${DATABASE_PASSWORD:-stage_password} - - JWT_SECRET=${JWT_SECRET:-changeme-stage-secret-key-must-be-at-least-256-bits} - - JWT_EXPIRATION=${JWT_EXPIRATION:-43200} - - JAVA_OPTS=-Xmx512m -Xms256m -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 - # MCP Server Configuration - Stage - - MCP_SERVER_ENABLED=${MCP_SERVER_ENABLED:-true} - - MCP_SERVER_NAME=${MCP_SERVER_NAME:-product-catalog-mcp-server-stage} - - MCP_SERVER_VERSION=${MCP_SERVER_VERSION:-2.0.0-stage} - - MCP_SSE_ENDPOINT=${MCP_SSE_ENDPOINT:-/sse} - depends_on: - postgres-db-stage: - condition: service_healthy - healthcheck: - # TODO: Use more comprehensive health check in the future (e.g., /actuator/health with proper business logic) - # Current: Uses Swagger UI endpoint - returns 200 OK, public access, lightweight - # Previous attempts: /api/v1/auth/login (405 Method Not Allowed - fails curl -f) - test: ["CMD", "curl", "-f", "http://localhost:8080/swagger-ui/index.html"] - interval: 30s - timeout: 15s - retries: 3 - start_period: 60s - networks: - - dokploy-network - deploy: - resources: - limits: - memory: 768M - cpus: '1.5' - reservations: - memory: 256M - cpus: '0.5' - restart_policy: - condition: on-failure - delay: 5s - max_attempts: 3 - labels: - - "traefik.enable=true" - - "traefik.http.routers.product-catalog-stage.rule=Host(`stage-product-catalog.thedavestack.com`)" - - "traefik.http.routers.product-catalog-stage.entrypoints=web,websecure" - - "traefik.http.routers.product-catalog-stage.tls=true" - - "traefik.http.routers.product-catalog-stage.tls.certresolver=letsencrypt" - - # Service configuration - - "traefik.http.services.product-catalog-stage.loadbalancer.server.port=8080" - - "traefik.http.services.product-catalog-stage.loadbalancer.healthcheck.path=/swagger-ui/index.html" - - "traefik.http.services.product-catalog-stage.loadbalancer.healthcheck.interval=30s" - - "traefik.http.services.product-catalog-stage.loadbalancer.healthcheck.timeout=10s" - - # SSE/MCP Server specific configuration for long-lived connections - - "traefik.http.routers.product-catalog-stage.middlewares=sse-headers-stage,sse-buffering-stage" - - # SSE Headers middleware - - "traefik.http.middlewares.sse-headers-stage.headers.customrequestheaders.Cache-Control=no-cache" - - "traefik.http.middlewares.sse-headers-stage.headers.customrequestheaders.Connection=keep-alive" - - "traefik.http.middlewares.sse-headers-stage.headers.customresponseheaders.Cache-Control=no-cache" - - "traefik.http.middlewares.sse-headers-stage.headers.customresponseheaders.Connection=keep-alive" - - "traefik.http.middlewares.sse-headers-stage.headers.customresponseheaders.Content-Type=text/event-stream" - - # SSE Buffering middleware (separate from headers) - - "traefik.http.middlewares.sse-buffering-stage.buffering.memrequestbodybytes=0" - - "traefik.http.middlewares.sse-buffering-stage.buffering.memresponsebodybytes=0" - - # Extended timeouts for SSE connections - - "traefik.http.services.product-catalog-stage.loadbalancer.responseforwarding.flushinterval=1s" - - "traefik.http.services.product-catalog-stage.loadbalancer.passhostheader=true" - - - "log.format=text" - -volumes: - postgres_stage_data: - driver: local - -networks: - dokploy-network: - external: true \ No newline at end of file diff --git a/docs/01-GETTING_STARTED.md b/docs/01-GETTING_STARTED.md index 3895d29..f261d82 100644 --- a/docs/01-GETTING_STARTED.md +++ b/docs/01-GETTING_STARTED.md @@ -80,12 +80,17 @@ mvn spring-boot:run Once the application is running, you can interact with it using `cURL` or any API client. -1. **Get an Admin JWT Token** +1. **Initial Setup Required** + The application uses database-managed users with BCrypt password hashing (V4 schema). + + **⚠️ First-Time Setup**: On first deployment, you'll need to create an initial admin user through the application's bootstrap process (implemented in task-14). + + **After Bootstrap Setup:** ```bash curl -X POST http://localhost:8080/api/v1/auth/login \ -H "Content-Type: application/json" \ - -d '{"username":"admin","password":"admin123"}' + -d '{"username":"your-admin-user","password":"your-secure-password"}' ``` This will return a JSON object with an `accessToken`. Copy the token for the next step. diff --git a/docs/03-API_REFERENCE.md b/docs/03-API_REFERENCE.md index bd16224..712a71c 100644 --- a/docs/03-API_REFERENCE.md +++ b/docs/03-API_REFERENCE.md @@ -6,9 +6,17 @@ This document provides a detailed reference for all available API endpoints. ## 🔐 Authentication -### Default Credentials: -- **Admin**: `admin` / `admin123` (Full CRUD access) -- **User**: `user` / `user123` (Read-only access) +### Database-Managed Users (V4 Schema) +The authentication system uses database-managed users with BCrypt password hashing, replacing the previous hardcoded system. + +### User Bootstrap Process: +The application requires initial user setup through a bootstrap service (implemented in task-14). No default users are seeded in migrations following enterprise security best practices. + +### Available Roles: +- **ADMIN**: Full CRUD operations, user management, role management, API key management, MCP access +- **MANAGER**: Write operations, delete operations, API key management, MCP access (no user management) +- **USER**: Read operations, MCP tools and resources, SSE access +- **READONLY**: Minimal read-only access, limited MCP resources | Method | Path | Description | Authorization | |--------|-------------------------|----------------------------------|---------------| @@ -17,12 +25,12 @@ This document provides a detailed reference for all available API endpoints. ## 📦 Product Management | Method | Path | Description | Authorization | |--------|--------------------------------|---------------------------------------|---------------| -| `POST` | `/products` | Create a new product | ADMIN | -| `GET` | `/products/{id}` | Retrieve a product by ID | USER/ADMIN | -| `GET` | `/products` | Retrieve products with pagination | USER/ADMIN | -| `GET` | `/products/low-stock` | Get products below minimum stock | USER/ADMIN | -| `PUT` | `/products/{id}` | Update an existing product | ADMIN | -| `DELETE`| `/products/{id}` | Soft delete a product | ADMIN | +| `POST` | `/products` | Create a new product | ADMIN/MANAGER | +| `GET` | `/products/{id}` | Retrieve a product by ID | All roles | +| `GET` | `/products` | Retrieve products with pagination | All roles | +| `GET` | `/products/low-stock` | Get products below minimum stock | All roles | +| `PUT` | `/products/{id}` | Update an existing product | ADMIN/MANAGER | +| `DELETE`| `/products/{id}` | Soft delete a product | ADMIN/MANAGER | ### 🔍 Advanced Query Parameters The `GET /products` endpoint supports the following query parameters for filtering, sorting, and pagination: @@ -40,11 +48,11 @@ The `GET /products` endpoint supports the following query parameters for filteri | Method | Path | Description | Authorization | |--------|--------------------------------|---------------------------------------|---------------| | `GET` | `/actuator/health` | Application health status with custom product health | Public | -| `GET` | `/actuator/info` | Enhanced application information with features | USER/ADMIN | -| `GET` | `/actuator/metrics` | Standard Spring Boot metrics | USER/ADMIN | -| `GET` | `/actuator/productmetrics` | Custom product catalog metrics | USER/ADMIN | -| `GET` | `/actuator/audit` | Audit log summary and recent entries | USER/ADMIN | -| `GET` | `/actuator/audit/{entityId}` | Audit logs for specific entity | USER/ADMIN | +| `GET` | `/actuator/info` | Enhanced application information with features | All roles | +| `GET` | `/actuator/metrics` | Standard Spring Boot metrics | All roles | +| `GET` | `/actuator/productmetrics` | Custom product catalog metrics | All roles | +| `GET` | `/actuator/audit` | Audit log summary and recent entries | ADMIN only | +| `GET` | `/actuator/audit/{entityId}` | Audit logs for specific entity | ADMIN only | | `GET` | `/swagger-ui/index.html` | Interactive API documentation | Public | | `GET` | `/v3/api-docs` | OpenAPI specification (JSON) | Public | diff --git a/docs/05-ARCHITECTURE_AND_DESIGN.md b/docs/05-ARCHITECTURE_AND_DESIGN.md index f14f5ea..c14ba53 100644 --- a/docs/05-ARCHITECTURE_AND_DESIGN.md +++ b/docs/05-ARCHITECTURE_AND_DESIGN.md @@ -31,7 +31,7 @@ com.thedavestack.productcatalog/ ## 🔒 Security Architecture - **JWT Stateless Authentication**: No server-side session storage, making the application scalable. -- **Role-Based Access Control**: Clear separation of duties between `ADMIN` (full CRUD) and `USER` (read-only) roles. +- **Enhanced Role-Based Access Control**: Database-managed users with 4 granular roles - `ADMIN` (full management), `MANAGER` (write access), `USER` (read + MCP), and `READONLY` (minimal access). - **Input Validation**: Multi-layer validation using Bean Validation annotations (`@Valid`) and custom validators for business rules. - **Developer-Friendly Errors**: 401 (Unauthorized) and 404 (Not Found) errors provide helpful links and guidance to the developer. diff --git a/src/main/resources/db/migration/V4__Create_users_roles_and_api_keys_tables.sql b/src/main/resources/db/migration/V4__Create_users_roles_and_api_keys_tables.sql new file mode 100644 index 0000000..9a4b214 --- /dev/null +++ b/src/main/resources/db/migration/V4__Create_users_roles_and_api_keys_tables.sql @@ -0,0 +1,85 @@ +-- Create users, roles, and API keys tables for authentication and authorization system + +-- Create roles table for normalized role management +CREATE TABLE roles ( + id VARCHAR(36) PRIMARY KEY, + name VARCHAR(50) UNIQUE NOT NULL, + description TEXT, + permissions TEXT[] NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +-- Create users table for database-managed users +CREATE TABLE users ( + id VARCHAR(36) PRIMARY KEY, + username VARCHAR(100) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + role_id VARCHAR(36) NOT NULL REFERENCES roles(id), + enabled BOOLEAN NOT NULL DEFAULT true, + mcp_access_enabled BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +-- Create API keys table for API key management +CREATE TABLE api_keys ( + id VARCHAR(36) PRIMARY KEY, + user_id VARCHAR(36) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + key_name VARCHAR(100) NOT NULL, + key_hash VARCHAR(255) NOT NULL, + key_prefix VARCHAR(10) NOT NULL, + scopes TEXT[] NOT NULL, + enabled BOOLEAN NOT NULL DEFAULT true, + expires_at TIMESTAMP WITH TIME ZONE, + last_used_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + UNIQUE(user_id, key_name) +); + +-- Create user role history table for audit trail of role changes +CREATE TABLE user_role_history ( + id VARCHAR(36) PRIMARY KEY, + user_id VARCHAR(36) NOT NULL REFERENCES users(id), + old_role_id VARCHAR(36) REFERENCES roles(id), + new_role_id VARCHAR(36) NOT NULL REFERENCES roles(id), + changed_by VARCHAR(36) REFERENCES users(id), + change_reason TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +-- Create indexes for performance optimization +CREATE INDEX idx_users_role_id ON users(role_id); +CREATE INDEX idx_users_username_enabled ON users(username, enabled); +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_api_keys_user_enabled ON api_keys(user_id, enabled); +CREATE INDEX idx_api_keys_prefix ON api_keys(key_prefix); +CREATE INDEX idx_api_keys_expires_at ON api_keys(expires_at); +CREATE INDEX idx_user_role_history_user_id ON user_role_history(user_id); +CREATE INDEX idx_user_role_history_created_at ON user_role_history(created_at DESC); + +-- NOTE: Data seeding is handled by application bootstrap service, not migrations +-- This follows enterprise best practices by separating schema evolution from business data + +-- Add check constraints for data integrity +ALTER TABLE roles ADD CONSTRAINT check_role_name +CHECK (name IN ('ADMIN', 'USER', 'MANAGER', 'READONLY')); + +ALTER TABLE api_keys ADD CONSTRAINT check_key_prefix_length +CHECK (LENGTH(key_prefix) >= 3 AND LENGTH(key_prefix) <= 10); + +ALTER TABLE api_keys ADD CONSTRAINT check_expires_at_future +CHECK (expires_at IS NULL OR expires_at > created_at); + +-- Add comments for documentation +COMMENT ON TABLE roles IS 'Normalized role management with granular permissions'; +COMMENT ON TABLE users IS 'Database-managed users with role-based access control'; +COMMENT ON TABLE api_keys IS 'API key management with scoped access and expiration'; +COMMENT ON TABLE user_role_history IS 'Audit trail for all user role changes'; + +COMMENT ON COLUMN roles.permissions IS 'Array of permission strings: READ, WRITE, DELETE, USER_MANAGEMENT, etc.'; +COMMENT ON COLUMN users.mcp_access_enabled IS 'Controls access to MCP (Model Context Protocol) server endpoints'; +COMMENT ON COLUMN api_keys.scopes IS 'Array of permission scopes for this API key'; +COMMENT ON COLUMN api_keys.key_prefix IS 'First few characters of API key for identification (not sensitive)'; +COMMENT ON COLUMN user_role_history.change_reason IS 'Human-readable reason for role change'; \ No newline at end of file