# Configurable Role Mapping Implementation ## Current State ✅ **Implemented:** - Basic OIDC group extraction from multiple claim sources - Hardcoded role mapping with comprehensive defaults - Database schema with groups column - Group storage during authentication - Hierarchical role assignment (highest privilege wins) ❌ **Missing:** - Runtime configurable role mapping from YAML config - Dynamic role mapping updates without code changes ## Recommended Implementation Strategy ### 1. **Configuration Reading Approach** Instead of forcing complex types through Arktype, implement configuration reading at the application layer: ```typescript // app/server/config/role-mapping.ts import { readFileSync } from 'fs'; import { parse } from 'yaml'; import type { Role } from '~/server/web/roles'; export interface RoleMappingConfig { role_mapping?: Record; } export function loadRoleMappingFromConfig(configPath?: string): Record | undefined { if (!configPath) return undefined; try { const configFile = readFileSync(configPath, 'utf-8'); const config = parse(configFile) as any; return config.oidc?.role_mapping; } catch (error) { console.warn('Failed to load role mapping from config:', error); return undefined; } } ``` ### 2. **Enhanced Role Mapping Function** Update the role mapping function to accept both built-in and external configurations: ```typescript // app/server/web/roles.ts export function mapOidcGroupsToRole( groups: string[], customMapping?: Record, configPath?: string ): Role { // Load from config file if provided const fileMapping = configPath ? loadRoleMappingFromConfig(configPath) : undefined; // Priority: customMapping > fileMapping > defaultMapping const mapping = customMapping || fileMapping || defaultMapping; // Rest of the function remains the same } ``` ### 3. **Application Integration** Update the OIDC callback to pass the config path: ```typescript // app/routes/auth/oidc-callback.ts const mappedRole = mapOidcGroupsToRole( user.groups, undefined, // custom mapping context.configPath // let function read from file ); ``` ## Alternative: Environment-Based Configuration For simpler deployments, support environment variables: ```typescript // Environment variables approach const roleMappingEnv = { HEADPLANE_ROLE_MAPPING_OWNER: 'ceo,cto,owners', HEADPLANE_ROLE_MAPPING_ADMIN: 'admins,administrators,it-admin', // etc. }; export function loadRoleMappingFromEnv(): Record { const mapping: Partial> = {}; Object.entries(roleMappingEnv).forEach(([key, defaultValue]) => { const role = key.replace('HEADPLANE_ROLE_MAPPING_', '').toLowerCase() as Role; const envValue = process.env[key] || defaultValue; mapping[role] = envValue.split(',').map(s => s.trim()); }); return mapping as Record; } ``` ## Benefits of This Approach ### 1. **Type Safety** - Maintains strict TypeScript types - Avoids Arktype readonly array conflicts - Clean separation of concerns ### 2. **Flexibility** - YAML configuration support - Environment variable fallback - Runtime configuration updates - Multiple configuration sources ### 3. **Backward Compatibility** - Existing hardcoded defaults still work - Progressive enhancement approach - No breaking changes for existing deployments ### 4. **Enterprise Features** - Hot-reload configuration changes - Multiple config file support - Validation and error handling - Audit trail for configuration changes ## Configuration Examples ### YAML Configuration (config.yaml) ```yaml oidc: issuer: "https://sso.company.com" client_id: "headplane" scope: "openid email profile groups" # Role mapping configuration role_mapping: owner: ["ceo", "cto", "founders"] admin: ["it-admins", "platform-team", "administrators"] network_admin: ["network-team", "devops", "sre"] it_admin: ["helpdesk", "support", "it-staff"] auditor: ["compliance", "security", "audit-team"] member: [] # Default for unmatched groups ``` ### Environment Variables (.env) ```bash HEADPLANE_ROLE_MAPPING_OWNER="ceo,cto,founders" HEADPLANE_ROLE_MAPPING_ADMIN="it-admins,platform-team,administrators" HEADPLANE_ROLE_MAPPING_NETWORK_ADMIN="network-team,devops,sre" HEADPLANE_ROLE_MAPPING_IT_ADMIN="helpdesk,support,it-staff" HEADPLANE_ROLE_MAPPING_AUDITOR="compliance,security,audit-team" ``` ## Implementation Priority 1. **Phase 1: File-based configuration** (Recommended for initial implementation) - Read role mapping from YAML config files - Maintain backward compatibility with defaults - Add configuration validation 2. **Phase 2: Environment variables** - Support ENV-based role mapping - Useful for containerized deployments - Override capability for specific roles 3. **Phase 3: Runtime updates** - Configuration hot-reload - Admin UI for role mapping management - Configuration versioning and rollback ## Next Steps To implement configurable role mapping: 1. Create `app/server/config/role-mapping.ts` with configuration loading logic 2. Update `mapOidcGroupsToRole` function to accept config path 3. Modify OIDC callback to pass configuration context 4. Add configuration validation and error handling 5. Update documentation with configuration examples 6. Add tests for different configuration scenarios This approach provides enterprise-grade configurability while maintaining the simplicity and backward compatibility that makes Headplane accessible to smaller deployments.