Skip to content

Implement first user onboarding mechanism#54

Merged
JoshuaAFerguson merged 11 commits into
mainfrom
claude/first-user-onboarding-01CNcixwHZuJKymiT4shcY2J
Nov 16, 2025
Merged

Implement first user onboarding mechanism#54
JoshuaAFerguson merged 11 commits into
mainfrom
claude/first-user-onboarding-01CNcixwHZuJKymiT4shcY2J

Conversation

@JoshuaAFerguson

Copy link
Copy Markdown
Member

No description provided.

…back system

Implements a production-ready admin user onboarding system with multiple
configuration methods for maximum flexibility and security.

## Multi-Layered Approach

Priority 1: Helm Chart (Production)
- Auto-generates secure 32-character random password
- Stores in Kubernetes Secret with 'keep' policy
- Displays retrieval command in Helm NOTES
- Supports custom passwords via values.yaml
- Supports existing secrets for external secret management

Priority 2: Environment Variable (Manual Deployments)
- Reads ADMIN_PASSWORD from environment
- Works with docker-compose, bare metal, CI/CD
- Validates password strength (8+ characters)
- Hashes with bcrypt before storage

Priority 3: Setup Wizard (First-Run & Recovery)
- Browser-based setup at /api/v1/auth/setup
- Only enabled when admin has no password
- Password strength validation (12+ characters for admin)
- Email validation and password confirmation
- Auto-disables after configuration
- Atomic database transaction prevents race conditions

## Password Reset

Implements ADMIN_PASSWORD_RESET for account recovery:
- Set environment variable with new password
- Restart API to apply reset
- Remove variable after reset completes
- Prominent logging for security awareness

## Implementation Details

### Helm Chart
- chart/templates/admin-credentials.yaml: Admin password secret
- chart/templates/app-secrets.yaml: General secrets (PostgreSQL, JWT)
- chart/templates/NOTES.txt: Updated with credentials retrieval
- chart/templates/api-deployment.yaml: Inject ADMIN_PASSWORD env var
- chart/values.yaml: Add auth.admin configuration section

### API Backend
- api/internal/db/database.go: ensureAdminPassword() cascading fallback
- api/internal/db/database.go: checkPasswordReset() for recovery
- api/internal/handlers/setup.go: Setup wizard API endpoints
  - GET /api/v1/auth/setup/status: Check if setup required
  - POST /api/v1/auth/setup: Complete admin configuration

### Documentation
- docs/ADMIN_ONBOARDING.md: Comprehensive 400+ line guide
  - All three onboarding methods with examples
  - Password reset procedures
  - Security best practices
  - Troubleshooting section
- QUICKSTART.md: Updated with admin login instructions

## Security Features

✅ Auto-generated random passwords (32 chars, high entropy)
✅ Bcrypt password hashing (cost 10)
✅ Password strength validation (8+ for env, 12+ for wizard)
✅ Kubernetes Secret encryption (if configured)
✅ helm.sh/resource-policy: keep (survives uninstall)
✅ Single-use setup wizard (auto-disables after use)
✅ Atomic database transactions (prevents race conditions)
✅ Input validation and sanitization
✅ No password exposure in logs or API responses

## Benefits

- **Production-Ready**: Helm chart with automated secret management
- **Flexible**: Works in any deployment environment
- **Recoverable**: Never get locked out of admin account
- **Secure**: Multiple layers of password protection
- **User-Friendly**: Setup wizard for non-technical users
- **Well-Documented**: 400+ line comprehensive guide

Closes #TBD
Implements the React UI components for the browser-based setup wizard,
completing the 3-tier admin onboarding system.

## Components Added

### SetupWizard.tsx (New Page)
- Browser-based admin password configuration interface
- Checks setup status on mount via API
- Auto-redirects if setup not required
- Password strength validation (12+ chars)
- Password confirmation field
- Email validation
- Loading and success states
- Informational panel explaining onboarding methods
- Material-UI styled with dark theme

Features:
- Real-time password strength validation
- Clear error messages with hints
- Auto-redirect to login after successful setup
- Responsive design with Container/Paper layout
- Progress indicators during API calls
- Common weak password detection
- Educational information about admin onboarding priority

### API Client Updates (api.ts)
- getSetupStatus(): Check if setup wizard should be enabled
- setupAdmin(): Submit admin credentials to backend

Returns:
  - setupRequired: boolean
  - adminExists: boolean
  - hasPassword: boolean
  - message: string

### Routing (App.tsx)
- Added public route: /setup
- Eagerly loaded (not lazy) for immediate access
- No authentication required (public route)

## User Flow

1. User visits /setup
2. Component checks setup status via API
3. If setup not required:
   - Show message
   - Auto-redirect to /login after 3 seconds
4. If setup required:
   - Display setup wizard form
   - Validate password strength (12+ chars)
   - Confirm password matches
   - Submit to backend API
   - Show success message
   - Redirect to /login after 2 seconds

## Integration with Backend

Connects to existing backend endpoints:
- GET /api/v1/auth/setup/status
- POST /api/v1/auth/setup

Handles backend errors:
- 403 Forbidden: Setup disabled
- 400 Bad Request: Validation errors
- 409 Conflict: Race condition (another request completed setup)
- 500 Internal Server Error: Database errors

## Security Features

✅ Password strength validation (12+ characters)
✅ Common weak password detection
✅ Password confirmation to prevent typos
✅ Email format validation
✅ Auto-disable after setup complete
✅ Clear error messages without exposing internals
✅ HTTPS recommended in production

## UI/UX Enhancements

- Loading spinner while checking status
- Progress bar when redirecting
- Success/error alerts with auto-dismiss
- Disabled state during submission
- Informational panel explaining fallback system
- Material-UI icons (AdminPanelSettings, Check, Info)
- Responsive layout for mobile/desktop
- Dark theme matching StreamSpace branding

Fixes #TBD (blank blue screen at /setup)
…ment script

The API expects DB_* prefix environment variables (DB_HOST, DB_PORT, etc.)
but the kubectl deployment script was using DATABASE_* prefix, causing
connection failures.

Changes:
- DATABASE_HOST → DB_HOST
- DATABASE_PORT → DB_PORT
- DATABASE_NAME → DB_NAME
- DATABASE_USER → DB_USER
- DATABASE_PASSWORD → DB_PASSWORD
- Added DB_SSLMODE=disable
- Added NAMESPACE from fieldRef

This aligns with the Helm chart configuration and allows the API to
connect to PostgreSQL successfully.

Fixes API CrashLoopBackOff: dial tcp [::1]:5432: connection refused
…source_quotas

Migration 173 was failing with "pq: syntax error at or near '('" because
PostgreSQL doesn't allow function calls like COALESCE() in inline UNIQUE
constraints.

Changed from:
  UNIQUE (user_id, COALESCE(team_id, ''))

To:
  CREATE UNIQUE INDEX ... ON resource_quotas(user_id, team_id) WHERE team_id IS NOT NULL
  CREATE UNIQUE INDEX ... ON resource_quotas(user_id) WHERE team_id IS NULL

This achieves the same uniqueness constraint (preventing duplicate quotas
for the same user/team combination) using valid PostgreSQL syntax.
…_versions

Migration 221 was failing with "column 'status' does not exist" because
there were two tables named `template_versions`:

1. Line 404: Simple version history for catalog templates (no status column)
2. Line 1314: Comprehensive versioning with status column for template testing

The first CREATE TABLE succeeded, second was skipped due to IF NOT EXISTS,
then the index creation for status column failed.

Renamed the first table to `catalog_template_versions` to clarify its
purpose and avoid naming conflict.

Now we have three distinct template version tables:
- catalog_template_versions: Repository template versions
- user_session_template_versions: User custom template versions
- template_versions: Main versioning system with status and testing support
Added RegisterRoutes method to SetupHandler and integrated it into the
main API route setup. The setup wizard endpoints are now properly
registered in the /api/v1/auth group alongside other authentication routes.

Changes:
- Added RegisterRoutes method to api/internal/handlers/setup.go
- Initialized SetupHandler in api/cmd/main.go
- Added setupHandler parameter to setupRoutes function
- Registered setup routes in authGroup (public, no auth required)

Routes available:
- GET  /api/v1/auth/setup/status - Check if setup wizard is enabled
- POST /api/v1/auth/setup        - Configure admin account

These routes are public (no authentication required) to allow initial
admin account setup before any users can log in.
Fixed Gin route registration panic caused by conflicting route patterns.
The routes `/versions/:versionId` and `/:templateId/versions` were
ambiguous and caused Gin to panic during route registration.

Changed all version-specific routes to be nested under template ID:

Before (conflicting):
- GET /:templateId/versions
- GET /versions/:versionId  <- CONFLICT

After (fixed):
- GET /:templateId/versions
- GET /:templateId/versions/:versionId

This makes semantic sense as versions belong to specific templates.
All version and test routes now follow the hierarchical pattern:
/:templateId/versions/:versionId/...

Fixes API startup panic at main.go:596
Fixed Gin panic caused by conflicting route parameter names. Gin doesn't
allow different parameter names at the same path position.

Changed all template versioning routes from :templateId to :id to match
the existing template CRUD routes that use 🆔

Routes:
- PATCH /api/v1/templates/:id (existing)
- DELETE /api/v1/templates/:id (existing)
- GET /api/v1/templates/:id/versions (new - now uses :id)

Also updated all handler methods to read c.Param("id") instead of
c.Param("templateId") in template_versioning.go:
- CreateTemplateVersion
- ListTemplateVersions
- GetTemplateInheritance

This resolves the panic:
':templateId' in new path conflicts with existing wildcard ':id'
Removed duplicate registration of POST /api/v1/sessions/:id/heartbeat
which was being registered in both:
1. main.go setupRoutes (line 397) - h.SessionHeartbeat
2. activity.go RegisterRoutes (line 74) - h.RecordHeartbeat

The ActivityHandler.RegisterRoutes() is the correct place for this route,
so removed it from the main setup to avoid Gin panic:
"handlers are already registered for path '/api/v1/sessions/:id/heartbeat'"

Added comment noting that heartbeat registration is delegated to
ActivityHandler.RegisterRoutes().
Removed duplicate registration of GET /api/v1/catalog/templates which was
being registered in both:
1. main.go setupRoutes (line 618) - h.BrowseCatalog
2. catalog.go RegisterRoutes (line 80) - h.ListTemplates

The CatalogHandler.RegisterRoutes() is the correct place for template
catalog routes, so removed it from main.go setup.

Kept repository management routes in main.go since CatalogHandler only
handles template-related endpoints:
- GET /catalog/repositories
- POST /catalog/repositories
- DELETE /catalog/repositories/:id
- POST /catalog/sync
- POST /catalog/install

Resolves Gin panic:
"handlers are already registered for path '/api/v1/catalog/templates'"
Fixed Gin panic caused by conflicting parameter names for session routes.
Changed session activity routes from :sessionId to :id to match the
existing session CRUD routes.

Route changes in main.go:
- /api/v1/sessions/:sessionId/activity -> /api/v1/sessions/:id/activity

Handler changes in sessionactivity.go:
- c.Param("sessionId") -> c.Param("id")

This resolves the panic:
':sessionId' in new path '/api/v1/sessions/:sessionId/activity/log'
conflicts with existing wildcard ':id' in existing prefix
'/api/v1/sessions/:id'

Note: Console routes (/console/sessions/:sessionId, /console/files/:sessionId)
are not affected as they're under a different path prefix.
@JoshuaAFerguson JoshuaAFerguson merged commit b9eb234 into main Nov 16, 2025
7 of 23 checks passed
@JoshuaAFerguson JoshuaAFerguson deleted the claude/first-user-onboarding-01CNcixwHZuJKymiT4shcY2J branch November 16, 2025 23:27
Comment thread chart/values.yaml
admin:
# Admin password (leave empty for auto-generation)
# ⚠️ SECURITY: For production, provide a secure password or use existingSecret
password: "" # Empty = auto-generate 32-character random password

Check failure

Code scanning / CodeQL

Empty password in configuration file High

Empty password in configuration file.
Comment on lines +17 to +22
import {
AdminPanelSettings as AdminIcon,
Check as CheckIcon,
Error as ErrorIcon,
Info as InfoIcon,
} from '@mui/icons-material';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import ErrorIcon.

Copilot Autofix

AI 7 months ago

To resolve this issue, the ErrorIcon should be removed from the named imports on line 20 in the @mui/icons-material import statement of ui/src/pages/SetupWizard.tsx. This change will clean up the code, reduce confusion, and prevent unnecessary imports. Only edit the lines shown; do not change or add other features or imports. There are no other changes or dependencies related to this fix.

Suggested changeset 1
ui/src/pages/SetupWizard.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/ui/src/pages/SetupWizard.tsx b/ui/src/pages/SetupWizard.tsx
--- a/ui/src/pages/SetupWizard.tsx
+++ b/ui/src/pages/SetupWizard.tsx
@@ -17,7 +17,6 @@
 import {
   AdminPanelSettings as AdminIcon,
   Check as CheckIcon,
-  Error as ErrorIcon,
   Info as InfoIcon,
 } from '@mui/icons-material';
 import { useNavigate } from 'react-router-dom';
EOF
@@ -17,7 +17,6 @@
import {
AdminPanelSettings as AdminIcon,
Check as CheckIcon,
Error as ErrorIcon,
Info as InfoIcon,
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants