headplane/SMART_IMPROVEMENTS.md
Ryan Malloy 7c21720519
Some checks are pending
Build / native (push) Waiting to run
Build / nix (push) Waiting to run
Complete the Astro rewrite
Drop the entire app/ Remix tree (144 deletions) and replace with the
Astro + Alpine.js architecture under src/. The Remix entrypoint, routes,
components, layouts, server bindings, and types are all gone; the Astro
pages (acls, dns, machines, settings, terminal, users, login, index)
plus their API endpoints under src/pages/api/ now own the surface.

Other surfaces touched:
- package.json: drop react-router, react-router-hono-server, remix-utils
  and the rest of the Remix stack; pull in Astro + integrations + Alpine
- pnpm-lock.yaml: regenerated against the new dependency set
- astro.config.mjs added; vite.config.ts, react-router.config.ts dropped
- New src/lib/auth/ (oidc-client, role-mapper, session-manager) and
  src/lib/config/authentik.ts for env-driven config
- biome.json: enable VCS-aware filtering, exclude .astro/dist/data/
  upstream/ and the React Router backup
- Extensive docs (HEADY_MANIFESTO, AUTHENTIK_*, BETTER_ROLE_MAPPING* etc.)
  and example role-mapping yamls added under examples/
- New remote-access/ tree for the Guacamole-Lite integration
- terminal.astro: prerender disabled (data is request-time only)

Committed with --no-verify; biome auto-fix was applied first but there
are still lint warnings in the new code worth a separate cleanup pass.
The legacy app/ tree was never re-pushed after the rewrite, which is
why the Gitea/Docker builds were trying to compile app/routes/ssh/
console.tsx.
2026-06-06 13:05:35 -06:00

7.5 KiB

Smart Improvements for OIDC Implementation

1. Intelligent Group Discovery

Instead of hardcoded claim parsing, use smart discovery:

// app/utils/smart-oidc.ts
export function discoverGroups(claims: any, userInfo: any): string[] {
  // Common group claim locations (ordered by reliability)
  const groupPaths = [
    'groups',                    // Standard OIDC
    'roles',                     // Common alternative
    'cognito:groups',           // AWS Cognito
    'resource_access.headplane.roles', // Keycloak client roles
    'realm_access.roles',       // Keycloak realm roles
    'azp_groups',               // Azure custom claim
    'memberOf',                 // LDAP style
    'teams',                    // GitHub style
  ];

  // Try userInfo first (more reliable), then claims
  for (const source of [userInfo, claims]) {
    for (const path of groupPaths) {
      const groups = getNestedValue(source, path);
      if (Array.isArray(groups) && groups.length > 0) {
        return groups.filter(g => typeof g === 'string');
      }
    }
  }
  
  return [];
}

function getNestedValue(obj: any, path: string): any {
  return path.split('.').reduce((current, key) => current?.[key], obj);
}

2. Self-Healing Configuration

Auto-fix common configuration mistakes:

// app/server/config/oidc-validator.ts
export function validateAndFixOidcConfig(config: any) {
  const fixes = [];
  
  // Auto-add groups scope if missing
  if (config.scope && !config.scope.includes('groups')) {
    config.scope += ' groups';
    fixes.push('Added "groups" to scope for role mapping');
  }
  
  // Auto-detect missing redirect_uri
  if (!config.redirect_uri && process.env.PUBLIC_URL) {
    config.redirect_uri = `${process.env.PUBLIC_URL}/admin/oidc/callback`;
    fixes.push('Auto-detected redirect_uri from PUBLIC_URL');
  }
  
  // Warn about common provider-specific issues
  if (config.issuer.includes('microsoft') && !config.scope.includes('profile')) {
    fixes.push('⚠️  Azure AD requires "profile" scope for user info');
  }
  
  return { config, fixes };
}

3. Smart Default Scope Detection

Instead of requiring manual scope configuration:

// app/utils/scope-detector.ts
export function detectOptimalScope(issuer: string): string {
  const baseScope = 'openid email profile';
  
  // Provider-specific optimizations
  const providerOptimizations = {
    'accounts.google.com': 'openid email profile',
    'login.microsoftonline.com': 'openid email profile groups',
    'keycloak': 'openid email profile groups roles',
    'okta.com': 'openid email profile groups',
    'auth0.com': 'openid email profile groups',
  };
  
  for (const [domain, scope] of Object.entries(providerOptimizations)) {
    if (issuer.includes(domain)) {
      return scope;
    }
  }
  
  return baseScope + ' groups'; // Safe default
}

4. Progressive Configuration UI

Instead of complex YAML editing, provide a setup wizard:

// app/routes/setup/oidc-wizard.tsx
export default function OidcWizard() {
  return (
    <WizardFlow>
      <Step title="Choose Provider">
        <ProviderButtons onSelect={(provider) => autoFillConfig(provider)} />
      </Step>
      
      <Step title="Connection Details">
        <SimpleForm fields={['issuer', 'client_id', 'client_secret']} />
      </Step>
      
      <Step title="Test & Deploy">
        <TestConnection />
        <OneClickDeploy />
      </Step>
    </WizardFlow>
  );
}

function autoFillConfig(provider: 'google' | 'azure' | 'keycloak' | 'okta') {
  const templates = {
    google: {
      issuer: 'https://accounts.google.com',
      scope: 'openid email profile',
      tips: ['Enable Google Workspace admin for groups']
    },
    azure: {
      issuer: 'https://login.microsoftonline.com/{tenant}/v2.0',
      scope: 'openid email profile groups',
      tips: ['Add groups claim to token configuration']
    }
    // ... etc
  };
  
  return templates[provider];
}

5. Intelligent Error Recovery

Instead of failing hard, provide helpful recovery:

// app/utils/oidc-recovery.ts
export function handleOidcError(error: any, context: any) {
  const recoveryStrategies = {
    'no_groups_found': {
      message: '👥 No groups found for this user',
      suggestions: [
        'Check if groups scope is included in OIDC configuration',
        'Verify user has groups assigned in identity provider',
        'Try environment variable: HEADPLANE_ADMIN_GROUPS="admin,managers"'
      ],
      autoFix: () => assignDefaultRole('member')
    },
    
    'invalid_issuer': {
      message: '🔗 Cannot connect to identity provider',
      suggestions: [
        'Verify issuer URL is correct',
        'Check if .well-known/openid-configuration endpoint exists',
        'Ensure network connectivity to provider'
      ],
      autoFix: () => suggestCommonIssuers(context.issuer)
    }
  };
  
  return recoveryStrategies[error.code] || defaultErrorHandler(error);
}

6. Environment-First Configuration

Make environment variables the primary configuration method:

# .env (single source of truth)
OIDC_ISSUER="https://your-provider.com"
OIDC_CLIENT_ID="headplane"
OIDC_CLIENT_SECRET="secret"

# Optional role mapping
HEADPLANE_ADMIN_GROUPS="admins,managers"
HEADPLANE_OWNER_GROUPS="ceo,founders"

# Auto-generated from above
OIDC_REDIRECT_URI="${PUBLIC_URL}/admin/oidc/callback"
OIDC_SCOPE="openid email profile groups"
// app/server/config/env-config.ts
export function loadOidcFromEnv() {
  const envConfig = {
    issuer: process.env.OIDC_ISSUER,
    client_id: process.env.OIDC_CLIENT_ID,
    client_secret: process.env.OIDC_CLIENT_SECRET,
    
    // Smart defaults
    redirect_uri: process.env.OIDC_REDIRECT_URI || 
                  `${process.env.PUBLIC_URL}/admin/oidc/callback`,
    scope: process.env.OIDC_SCOPE || 
           detectOptimalScope(process.env.OIDC_ISSUER),
  };
  
  return validateAndFixOidcConfig(envConfig);
}

7. Zero-Config Development Mode

For local development, eliminate configuration entirely:

// app/server/dev-mode.ts
export function createDevOidcProvider() {
  if (process.env.NODE_ENV !== 'development') return null;
  
  return {
    issuer: 'http://localhost:3001/dev-oidc',
    client_id: 'dev',
    client_secret: 'dev',
    mock_users: [
      { email: 'admin@dev.local', groups: ['admin'] },
      { email: 'user@dev.local', groups: ['member'] },
      { email: 'owner@dev.local', groups: ['owner'] }
    ]
  };
}

Benefits of These Improvements

Immediate Impact

  • Zero-Config for 80% of use cases: Most deployments work with just issuer + credentials
  • Self-Healing: Common mistakes are automatically fixed with helpful messages
  • Environment-First: Perfect for containers and cloud deployments
  • Progressive Enhancement: Start simple, add complexity only when needed

Developer Experience

  • Instant Feedback: Real-time validation and testing
  • Helpful Errors: Solution-oriented error messages
  • Smart Defaults: Sensible choices that work for most organizations
  • One-Click Setup: Guided setup for popular providers

Production Benefits

  • Robust Error Handling: Graceful degradation instead of hard failures
  • Auto-Discovery: Reduces configuration surface area
  • Multi-Environment: Same config works across dev/staging/production
  • Monitoring Friendly: Clear logging and metrics

These improvements follow the same "convention over configuration" philosophy that made our role mapping elegant, but apply it across the entire OIDC experience.