diff --git a/docs/development/backend/gateway-client-config.mdx b/docs/development/backend/gateway-client-config.mdx new file mode 100644 index 0000000..06f42d4 --- /dev/null +++ b/docs/development/backend/gateway-client-config.mdx @@ -0,0 +1,78 @@ +--- +title: Gateway Client Configuration API +description: Developer guide for the Gateway Client Configuration endpoint that provides pre-formatted configuration files for MCP clients to connect to the DeployStack Gateway +--- + +# Gateway Client Configuration API + +The Gateway Client Configuration API provides pre-formatted configuration files for various MCP clients to connect to the local DeployStack Gateway. This eliminates manual configuration steps and reduces setup errors for developers. + +## Overview + +The endpoint generates client-specific JSON configurations that users can directly use in their MCP clients (Claude Desktop, Cline, VSCode, Cursor, Windsurf) to connect to their local DeployStack Gateway running on `http://localhost:9095/sse`. + +## API Endpoint + +**Route:** `GET /api/gateway/config/:client` + +**Parameters:** +- `:client` - The MCP client type (required) + - Supported values: `claude-desktop`, `cline`, `vscode`, `cursor`, `windsurf` + +**Authentication:** Dual authentication support +- Cookie-based authentication (web users) +- OAuth2 Bearer token authentication (CLI users) + +**Required Permission:** `gateway.config:read` +**Required OAuth2 Scope:** `gateway:config:read` + +## Client Configurations + +The endpoint supports multiple MCP client types, each with its own optimized configuration format. All configurations use the DeployStack Gateway's SSE endpoint: `http://localhost:9095/sse` + +**Supported Clients:** +- `claude-desktop` - Claude Desktop application +- `cline` - Cline VS Code extension +- `vscode` - VS Code MCP extension +- `cursor` - Cursor IDE +- `windsurf` - Windsurf AI IDE + +**Configuration Source:** For the current client configuration formats and JSON structures, see the `generateClientConfig()` function in: + +```bash +services/backend/src/routes/gateway/config/get-client-config.ts +``` + +## Permission System + +### Role Assignments + +The `gateway.config:read` permission is assigned to: +- **global_admin** - Basic access to get gateway configs they need +- **global_user** - Basic access to get gateway configs they need + +### OAuth2 Scope + +The `gateway:config:read` OAuth2 scope: +- **Purpose:** Enables CLI tools and OAuth2 clients to fetch gateway configurations +- **Description:** "Generate client-specific gateway configuration files" +- **Gateway Integration:** Automatically requested during gateway OAuth login + +## Future Enhancements + +### Additional Client Types +- **Zed Editor** - Growing popularity in developer community +- **Neovim** - Popular among command-line developers +- **Custom Clients** - Generic JSON format for custom integrations + +### Configuration Customization +- **Environment Variables** - Support for different gateway URLs +- **TLS/SSL Support** - When gateway supports secure connections +- **Authentication Tokens** - If gateway adds authentication in future + +## Related Documentation + +- [Backend API Security](/development/backend/api-security) - Security patterns and authorization +- [Backend OAuth2 Server](/development/backend/oauth2-server) - OAuth2 implementation details +- [Gateway OAuth Implementation](/development/gateway/oauth) - Gateway OAuth client +- [Gateway SSE Transport](/development/gateway/sse-transport) - Gateway SSE architecture diff --git a/docs/development/backend/index.mdx b/docs/development/backend/index.mdx index 6b4cca0..12cb1eb 100644 --- a/docs/development/backend/index.mdx +++ b/docs/development/backend/index.mdx @@ -5,7 +5,7 @@ sidebar: Getting Started --- import { Card, Cards } from 'fumadocs-ui/components/card'; -import { Database, Shield, Plug, Settings, Mail, TestTube, Wrench, BookOpen } from 'lucide-react'; +import { Database, Shield, Plug, Settings, Mail, TestTube, Wrench, BookOpen, Terminal } from 'lucide-react'; # DeployStack Backend Development @@ -96,6 +96,14 @@ The development server starts at `http://localhost:3000` with API documentation > User roles, permissions system, and access control implementation details. + + } + href="/deploystack/development/backend/gateway-client-config" + title="Gateway Client Configuration" + > + API endpoint for generating client-specific gateway configuration files with dual authentication support. + ## Project Structure diff --git a/docs/development/backend/mail.mdx b/docs/development/backend/mail.mdx index e5501c7..54cebaf 100644 --- a/docs/development/backend/mail.mdx +++ b/docs/development/backend/mail.mdx @@ -51,17 +51,9 @@ The email system automatically integrates with your existing SMTP global setting ## Basic Usage -### Import the Email Service - ```typescript import { EmailService } from '../email'; -// or -import EmailService from '../email'; -``` - -### Send a Basic Email -```typescript const result = await EmailService.sendEmail({ to: 'user@example.com', subject: 'Welcome to DeployStack', @@ -69,139 +61,40 @@ const result = await EmailService.sendEmail({ variables: { userName: 'John Doe', userEmail: 'user@example.com', - loginUrl: 'https://app.deploystack.com/login', - supportEmail: 'support@deploystack.com' + loginUrl: 'https://app.deploystack.io/login' } }); if (result.success) { - request.log.info({ - messageId: result.messageId, - recipients: result.recipients, - operation: 'send_email' - }, 'Email sent successfully'); + request.log.info('Email sent successfully'); } else { - request.log.error({ - error: result.error, - recipients: result.recipients, - operation: 'send_email' - }, 'Failed to send email'); + request.log.error('Failed to send email:', result.error); } ``` -### Send to Multiple Recipients - -```typescript -const result = await EmailService.sendEmail({ - to: ['user1@example.com', 'user2@example.com'], - subject: 'System Maintenance Notice', - template: 'notification', - variables: { - title: 'Scheduled Maintenance', - message: 'We will be performing system maintenance on Sunday at 2 AM UTC.', - actionUrl: 'https://status.deploystack.com', - actionText: 'View Status Page' - } -}); -``` - ## Type-Safe Helper Methods -The email service provides type-safe helper methods for common email types: - -### Welcome Email +### Available Methods +- `sendWelcomeEmail(options)` - Welcome email for new users +- `sendPasswordResetEmail(options)` - Password reset instructions +- `sendNotificationEmail(options)` - General notifications ```typescript +// Example usage const result = await EmailService.sendWelcomeEmail({ to: 'newuser@example.com', userName: 'Jane Smith', userEmail: 'newuser@example.com', - loginUrl: 'https://app.deploystack.com/login', - supportEmail: 'support@deploystack.com' // optional -}); -``` - -### Password Reset Email - -```typescript -const result = await EmailService.sendPasswordResetEmail({ - to: 'user@example.com', - userName: 'John Doe', - resetUrl: 'https://app.deploystack.com/reset-password?token=abc123', - expirationTime: '24 hours', - supportEmail: 'support@deploystack.com' // optional -}); -``` - -### Notification Email - -```typescript -const result = await EmailService.sendNotificationEmail({ - to: 'user@example.com', - title: 'Deployment Complete', - message: 'Your application has been successfully deployed to production.', - actionUrl: 'https://app.deploystack.com/deployments/123', - actionText: 'View Deployment', - userName: 'John Doe' // optional -}); -``` - -## Advanced Usage - -### Custom From Address - -```typescript -const result = await EmailService.sendEmail({ - to: 'user@example.com', - subject: 'Custom Sender Example', - template: 'notification', - from: { - name: 'DeployStack Notifications', - email: 'notifications@deploystack.com' - }, - variables: { - title: 'Custom Message', - message: 'This email is sent from a custom sender.' - } + loginUrl: 'https://app.deploystack.io/login' }); ``` -### Email with Attachments +## Advanced Features -```typescript -const result = await EmailService.sendEmail({ - to: 'user@example.com', - subject: 'Report Attached', - template: 'notification', - variables: { - title: 'Monthly Report', - message: 'Please find your monthly deployment report attached.' - }, - attachments: [ - { - filename: 'report.pdf', - content: reportBuffer, - contentType: 'application/pdf' - } - ] -}); -``` - -### CC and BCC Recipients - -```typescript -const result = await EmailService.sendEmail({ - to: 'primary@example.com', - cc: ['manager@example.com'], - bcc: ['audit@example.com'], - subject: 'Important Update', - template: 'notification', - variables: { - title: 'System Update', - message: 'Important system update notification.' - } -}); -``` +- **Custom From Address**: Override default sender information +- **Multiple Recipients**: Send to arrays of email addresses +- **Attachments**: Include files with emails +- **CC/BCC**: Additional recipient types supported ## Template System @@ -221,97 +114,17 @@ All templates have access to these common variables: - `appName`: Application name (default: 'DeployStack') - `supportEmail`: Support email address (if provided) -### Creating Custom Templates - -1. **Create a new Pug template** in `src/email/templates/`: - -```pug -//- custom-template.pug -//- @description Custom email template -//- @variables customVar1, customVar2 -extends layouts/base.pug - -block content - h1 Custom Email - - p Hello #{customVar1}! - - p= customVar2 - - .text-center - a.button(href="https://example.com") Take Action -``` - -2. **Add TypeScript types** in `src/email/types.ts`: - -```typescript -export interface CustomEmailVariables { - customVar1: string; - customVar2: string; -} - -// Add to TemplateVariableMap -export interface TemplateVariableMap { - welcome: WelcomeEmailVariables; - 'password-reset': PasswordResetEmailVariables; - notification: NotificationEmailVariables; - 'custom-template': CustomEmailVariables; // Add this line -} -``` - -3. **Use the custom template**: - -```typescript -const result = await EmailService.sendEmail({ - to: 'user@example.com', - subject: 'Custom Email', - template: 'custom-template', - variables: { - customVar1: 'John', - customVar2: 'This is a custom message.' - } -}); -``` - -## Template Layout System - -### Base Layout - -The base layout (`layouts/base.pug`) provides: - -- Responsive HTML email structure -- Cross-client CSS compatibility -- Header and footer inclusion -- Mobile-friendly design -- Professional styling - -### Header Component - -The header (`layouts/header.pug`) displays: +### Custom Templates -- Application name/logo -- Consistent branding -- Professional appearance +To create custom templates: -### Footer Component +1. Add new Pug template in `src/email/templates/` +2. Add TypeScript types in `src/email/types.ts` +3. Use with `EmailService.sendEmail()` -The footer (`layouts/footer.pug`) includes: +#### Color Guidelines -- Copyright information -- Contact information -- Unsubscribe/support links -- Legal disclaimers - -### Customizing Layouts - -To customize the layout, modify the files in `src/email/templates/layouts/`: - -```pug -//- layouts/header.pug -.header - img(src="https://your-domain.com/logo.png" alt="Your Logo" style="height: 40px;") - h1= appName || 'Your App Name' -``` +When adding colors to email templates, follow the official DeployStack color system to maintain brand consistency. If you want to check what our primary colors are, visit the [UI Design System](/development/frontend/ui-design-system) page. ## Utility Methods @@ -320,349 +133,50 @@ To customize the layout, modify the files in `src/email/templates/layouts/`: ```typescript const status = await EmailService.testConnection(request.log); if (status.success) { - request.log.info({ - operation: 'test_smtp_connection' - }, 'SMTP connection successful'); -} else { - request.log.error({ - error: status.error, - operation: 'test_smtp_connection' - }, 'SMTP connection failed'); -} -``` - -### Check SMTP Configuration - -```typescript -const status = await EmailService.getSmtpStatus(request.log); -if (status.configured) { - request.log.info({ - operation: 'check_smtp_status' - }, 'SMTP is configured'); + request.log.info('SMTP connection successful'); } else { - request.log.error({ - error: status.error, - operation: 'check_smtp_status' - }, 'SMTP not configured'); + request.log.error('SMTP connection failed:', status.error); } ``` -### Refresh Configuration +### Other Utilities -```typescript -// Call this after updating SMTP settings -await EmailService.refreshConfiguration(request.log); -``` - -### Get Available Templates - -```typescript -const templates = EmailService.getAvailableTemplates(request.log); -request.log.info({ - templates, - templateCount: templates.length, - operation: 'get_available_templates' -}, 'Available templates retrieved'); -// Output: ['welcome', 'password-reset', 'notification'] -``` - -### Validate Template - -```typescript -const validation = await EmailService.validateTemplate('welcome', { - userName: 'John', - userEmail: 'john@example.com', - loginUrl: 'https://app.com/login' -}); - -if (validation.valid) { - request.log.info({ - template: 'welcome', - operation: 'validate_template' - }, 'Template is valid'); -} else { - request.log.error({ - template: 'welcome', - errors: validation.errors, - missingVariables: validation.missingVariables, - operation: 'validate_template' - }, 'Template validation failed'); -} -``` +- `getSmtpStatus()` - Check SMTP configuration status +- `refreshConfiguration()` - Reload SMTP configuration +- `getAvailableTemplates()` - Get list of available templates +- `validateTemplate()` - Validate template and variables ## Error Handling -The email service provides comprehensive error handling: - -```typescript -const result = await EmailService.sendEmail({ - to: 'invalid-email', - subject: 'Test', - template: 'welcome', - variables: { userName: 'Test' } -}); - -if (!result.success) { - switch (result.error) { - case 'SMTP configuration is not available or invalid': - // Handle SMTP configuration issues - break; - case 'Template \'welcome\' not found': - // Handle missing template - break; - default: - // Handle other errors - request.log.error({ - error: result.error, - operation: 'send_email' - }, 'Email failed'); - } -} -``` - -## Performance Considerations - -### Template Caching - -Templates are automatically cached after first compilation: - -```typescript -// First call compiles and caches the template -await EmailService.sendEmail({ template: 'welcome', ... }); - -// Subsequent calls use cached template (faster) -await EmailService.sendEmail({ template: 'welcome', ... }); -``` - -### Clear Cache (Development) - -```typescript -import { TemplateRenderer } from '../email'; - -// Clear template cache during development -TemplateRenderer.clearCache(); -``` - -### Connection Pooling - -The email service uses connection pooling for better performance: - -- Maximum 5 concurrent connections -- Maximum 100 messages per connection -- Rate limiting: 5 emails per 20 seconds - -## Security Best Practices +The email service provides comprehensive error handling with specific error messages for: -### Input Validation +- SMTP configuration issues +- Missing templates +- Invalid email addresses +- Connection failures -All email parameters are validated using Zod schemas: +## Security & Performance -```typescript -// This will throw a validation error -await EmailService.sendEmail({ - to: 'invalid-email-format', // ❌ Invalid email - subject: '', // ❌ Empty subject - template: '', // ❌ Empty template - variables: {} -}); -``` - -### Template Security - -- Templates are compiled server-side (no client-side execution) -- Variable injection is escaped by default -- No arbitrary code execution in templates - -### SMTP Security +### Security Features +- Passwords encrypted in global settings +- Secure connections (TLS/SSL) supported +- Template security with escaped variable injection -- Passwords are encrypted in global settings -- Secure connections (TLS/SSL) are supported -- Connection pooling with rate limiting +### Performance Features +- Template caching after first compilation +- Connection pooling (max 5 concurrent connections) +- Rate limiting (5 emails per 20 seconds) -## Integration Examples +## Common Usage Patterns ### User Registration +Send welcome emails after user account creation. -```typescript -// In your user registration service -import { EmailService } from '../email'; - -export class UserService { - static async registerUser(userData: UserRegistrationData) { - // Create user account - const user = await this.createUser(userData); - - // Send welcome email - const emailResult = await EmailService.sendWelcomeEmail({ - to: user.email, - userName: user.name, - userEmail: user.email, - loginUrl: `${process.env.FRONTEND_URL}/login`, - supportEmail: 'support@deploystack.com' - }); - - if (!emailResult.success) { - request.log.error({ - error: emailResult.error, - userId: user.id, - operation: 'send_welcome_email' - }, 'Failed to send welcome email'); - // Don't fail registration if email fails - } - - return user; - } -} -``` - -### Password Reset Flow - -```typescript -// In your auth service -import { EmailService } from '../email'; - -export class AuthService { - static async requestPasswordReset(email: string) { - const user = await this.findUserByEmail(email); - if (!user) { - throw new Error('User not found'); - } - - // Generate reset token - const resetToken = await this.generateResetToken(user.id); - const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`; - - // Send reset email - const emailResult = await EmailService.sendPasswordResetEmail({ - to: user.email, - userName: user.name, - resetUrl, - expirationTime: '24 hours', - supportEmail: 'support@deploystack.com' - }); - - if (!emailResult.success) { - throw new Error('Failed to send password reset email'); - } - - return { message: 'Password reset email sent' }; - } -} -``` - -### Deployment Notifications - -```typescript -// In your deployment service -import { EmailService } from '../email'; - -export class DeploymentService { - static async notifyDeploymentComplete(deploymentId: string) { - const deployment = await this.getDeployment(deploymentId); - const user = await this.getUser(deployment.userId); - - const emailResult = await EmailService.sendNotificationEmail({ - to: user.email, - title: 'Deployment Complete', - message: `Your deployment "${deployment.name}" has been successfully completed.`, - actionUrl: `${process.env.FRONTEND_URL}/deployments/${deploymentId}`, - actionText: 'View Deployment', - userName: user.name - }); - - if (!emailResult.success) { - request.log.error({ - error: emailResult.error, - deploymentId, - userId: user.id, - operation: 'send_deployment_notification' - }, 'Failed to send deployment notification'); - } - } -} -``` - -## Troubleshooting - -### Common Issues - -1. **SMTP Configuration Not Found** - - ```text - Error: SMTP configuration is not complete. Please configure SMTP settings in global settings. - ``` - - **Solution**: Configure SMTP settings in the global settings interface. - -2. **Template Not Found** - - ```text - Error: Template 'welcome' not found at /path/to/templates/welcome.pug - ``` - - **Solution**: Ensure the template file exists in the templates directory. - -3. **Invalid Email Address** - - ```text - Error: Invalid email address - ``` - - **Solution**: Validate email addresses before sending. - -4. **SMTP Connection Failed** - - ```text - Error: Connection timeout - ``` - - **Solution**: Check SMTP server settings and network connectivity. - -### Debug Mode - -Enable debug logging for email operations: - -```typescript -// Set environment variable -process.env.DEBUG_EMAIL = 'true'; - -// Or log email results -const result = await EmailService.sendEmail({...}, request.log); -request.log.debug({ - success: result.success, - messageId: result.messageId, - recipients: result.recipients, - operation: 'send_email' -}, 'Email result'); -``` - -### Testing SMTP Configuration - -```typescript -// Test SMTP connection before sending emails -const connectionTest = await EmailService.testConnection(request.log); -if (!connectionTest.success) { - request.log.error({ - error: connectionTest.error, - operation: 'test_smtp_connection' - }, 'SMTP test failed'); - return; -} - -// Proceed with sending emails -const emailResult = await EmailService.sendEmail({...}, request.log); -``` - -## Best Practices +### Password Reset +Generate reset tokens and send secure reset links. -1. **Always handle email failures gracefully** - Don't let email failures break your main application flow -2. **Use type-safe helper methods** when possible for better developer experience -3. **Test email templates** in different email clients for compatibility -4. **Monitor email delivery** and set up alerts for failures -5. **Use meaningful subject lines** and clear call-to-action buttons -6. **Respect user preferences** for email notifications -7. **Keep templates simple** and mobile-friendly -8. **Cache templates** in production for better performance +### System Notifications +Alert users about deployments, system updates, or important events. ## API Reference @@ -692,4 +206,4 @@ const emailResult = await EmailService.sendEmail({...}, request.log); --- -For more information about global settings configuration, see [GLOBAL_SETTINGS](/development/backend/global-settings). +For more information about global settings configuration, see [Global Settings](/development/backend/global-settings). diff --git a/docs/development/backend/oauth2-server.mdx b/docs/development/backend/oauth2-server.mdx index baeb7b8..3c7494c 100644 --- a/docs/development/backend/oauth2-server.mdx +++ b/docs/development/backend/oauth2-server.mdx @@ -154,13 +154,7 @@ For complete API specifications including request parameters, response schemas, ### Available Scopes -Fine-grained permissions for API access: - -- `mcp:read` - Read MCP server configurations -- `account:read` - Read account information -- `user:read` - Read user profile -- `teams:read` - Read team memberships -- `offline_access` - Refresh token support +For the current list of supported scopes, see the source code at `services/backend/src/services/oauth/authorizationService.ts` in the `validateScope()` method. ### Scope Enforcement @@ -321,6 +315,75 @@ Comprehensive logging for debugging: - Error conditions - Security events +## OAuth Scope Management + +The backend validates OAuth scopes to control API access. Scope configuration must stay synchronized between the backend and gateway. + +### Current Scopes + +For the current list of supported scopes, check the source code at: +- **Backend validation**: `services/backend/src/services/oauth/authorizationService.ts` in the `validateScope()` method +- **Gateway requests**: `services/gateway/src/utils/auth-config.ts` in the `scopes` array + +### Adding New Scopes + +When adding support for a new OAuth scope in the backend: + +1. **Add the scope** to the `allowedScopes` array in `services/backend/src/services/oauth/authorizationService.ts` +2. **Update the gateway** to request the new scope (see [Gateway OAuth Implementation](/development/gateway/oauth)) +3. **Apply scope enforcement** to relevant API endpoints using middleware +4. **Test the complete flow** to ensure proper scope validation + +Example: +```typescript +// In services/backend/src/services/oauth/authorizationService.ts +static validateScope(scope: string): boolean { + const requestedScopes = scope.split(' '); + const allowedScopes = [ + 'mcp:read', + 'mcp:categories:read', + 'your-new-scope', // Add new scope here + // ... other scopes + ]; + + return requestedScopes.every(s => allowedScopes.includes(s)); +} +``` + +### Scope Enforcement in Routes + +Apply scope requirements to API endpoints: + +```typescript +// Single scope requirement +server.get('/api/your-endpoint', { + preValidation: [ + requireValidAccessToken(), + requireOAuthScope('your-new-scope') + ] +}, async (request, reply) => { + // Your endpoint logic +}); + +// Multiple scope options +server.get('/api/another-endpoint', { + preValidation: [ + requireValidAccessToken(), + requireAnyOAuthScope(['scope1', 'scope2']) + ] +}, async (request, reply) => { + // Your endpoint logic +}); +``` + +### Scope Synchronization + +**Critical**: The backend and gateway must have matching scope configurations: +- If backend supports a scope but gateway doesn't request it, users won't get that permission +- If gateway requests a scope but backend doesn't support it, authentication will fail + +Always coordinate scope changes between both services. + ## Gateway Integration The OAuth2 server integrates with the DeployStack Gateway: diff --git a/docs/development/backend/user-preferences-system.mdx b/docs/development/backend/user-preferences-system.mdx new file mode 100644 index 0000000..b531dbf --- /dev/null +++ b/docs/development/backend/user-preferences-system.mdx @@ -0,0 +1,288 @@ +--- +title: User Preferences System +description: Developer guide for managing user preferences in DeployStack Backend - adding new preferences, using the service layer, and understanding the architecture. +--- + +# User Preferences System + +The User Preferences System provides a flexible, config-driven approach to managing user-specific settings and behavioral data in DeployStack. This system handles everything from onboarding states to UI preferences without requiring database migrations for new preferences. + +## System Overview + +We use a separate table architecture where each preference is stored as an individual row, providing excellent queryability and performance. The system is designed around three core principles: + +- **Config-Driven**: New preferences require only configuration changes, no database migrations +- **Type-Safe**: Full TypeScript integration with runtime validation +- **Self-Service**: Users manage only their own preferences with proper security isolation + +## Architecture Components + +### Configuration Layer +All preferences are defined in `/src/config/user-preferences.ts` as the single source of truth. This file contains default values and type definitions for all available preferences. + +### Service Layer +The `UserPreferencesService` handles all database operations, type conversion, and business logic for preference management. + +### API Layer +RESTful endpoints provide secure access to preferences with permission-based authorization. + +### Database Layer +The `userPreferences` table stores individual key-value pairs with proper indexing for performance. + +## Adding New Preferences + +Adding a new preference is remarkably simple and requires no database migrations. + +### Step 1: Update Configuration + +Edit `/src/config/user-preferences.ts` and add your new preference to the `DEFAULT_USER_PREFERENCES` object: + +```typescript +export const DEFAULT_USER_PREFERENCES = { + // Existing preferences... + show_survey_overall: true, + show_survey_company: true, + + // Your new preferences + new_feature_enabled: false, + user_language: 'en', + dashboard_layout: 'grid', + notification_frequency: 'daily', +} as const; +``` + +### Step 2: Restart Application + +That's it! Restart the application and: +- New users automatically receive the new preferences with default values +- Existing users can access the new preferences (they'll get defaults when first accessed) +- No database migration required +- **API schemas and TypeScript types are automatically updated** from your config changes + +### Single Source of Truth + +The system automatically generates API validation schemas and TypeScript interfaces from the configuration file. This means: + +- **No duplication**: You only define preferences in one place +- **Completely generic**: Works with any preference type (string, boolean, number) +- **No special cases**: All preferences are handled uniformly, no hardcoded values +- **Automatic validation**: API endpoints automatically validate against your config +- **Type safety**: TypeScript interfaces are auto-generated from your preferences +- **Zero maintenance**: Adding/removing preferences doesn't require schema updates + +## Using the Service Layer + +The `UserPreferencesService` provides a clean interface for working with preferences in your application code. + +### Basic Operations + +```typescript +import { UserPreferencesService } from '../services/UserPreferencesService'; +import { getDb } from '../db'; + +const db = getDb(); +const preferencesService = new UserPreferencesService(db); + +// Get a specific preference with fallback default +const theme = await preferencesService.getPreference(userId, 'theme', 'auto'); + +// Set a single preference +await preferencesService.setPreference(userId, 'show_survey_overall', false); + +// Update multiple preferences at once +await preferencesService.updatePreferences(userId, { + theme: 'dark', + sidebar_collapsed: true, + email_notifications_enabled: false +}); + +// Get all user preferences +const allPreferences = await preferencesService.getUserPreferences(userId); +``` + +### Specialized Methods + +```typescript +// Walkthrough management +const shouldShow = await preferencesService.shouldShowWalkthrough(userId); +await preferencesService.completeWalkthrough(userId); +await preferencesService.cancelWalkthrough(userId); + +// Notification acknowledgments +await preferencesService.acknowledgeNotification(userId, 'welcome-2024'); +``` + +### Integration in Route Handlers + +```typescript +export default async function myRoute(server: FastifyInstance) { + server.get('/my-feature', { + preValidation: requirePermission('preferences.view'), + }, async (request, reply) => { + const userId = request.user!.id; + const db = getDb(); + const preferencesService = new UserPreferencesService(db); + + // Check if user has enabled the feature + const featureEnabled = await preferencesService.getPreference( + userId, + 'new_feature_enabled', + false + ); + + if (!featureEnabled) { + return reply.status(403).send({ error: 'Feature not enabled' }); + } + + // Continue with feature logic... + }); +} +``` + +## Security Model + +The User Preferences System implements a strict self-service security model with important restrictions: + +### Core Security Principle: Self-Service Only +- **Users can ONLY view and modify their own preferences** +- **Even `global_admin` users CANNOT access other users' preferences** +- **No admin override capability exists (by design for privacy)** +- **All preference operations are strictly user-scoped** + +### Permissions Required +- `preferences.view` - Required to read own preferences +- `preferences.edit` - Required to modify own preferences + +### Role Assignments +- `global_admin` - Has both view and edit permissions (for their own preferences only) +- `global_user` - Has both view and edit permissions (for their own preferences only) +- Team roles - No preference permissions (preferences are user-scoped, not team-scoped) + +### Access Control Implementation +- All routes extract `userId` from the authenticated user's session (`request.user!.id`) +- No route accepts a `userId` parameter - it's always the authenticated user +- Permission-based authorization happens before validation (security-first pattern) +- Database queries are automatically scoped to the authenticated user's ID + +### What This Means for Developers +- You cannot build admin tools to manage other users' preferences +- Support teams cannot directly modify user preferences through the API +- All preference management is strictly self-service +- Users have complete privacy and control over their preference data + +## Current Available Preferences + +The system currently supports these preference categories: + +### Survey Preferences +- `show_survey_overall` (boolean) - Display overall satisfaction survey +- `show_survey_company` (boolean) - Display company-specific survey + +### Walkthrough Preferences +- `walkthrough_completed` (boolean) - User completed onboarding walkthrough +- `walkthrough_cancelled` (boolean) - User cancelled onboarding walkthrough + +### UI Preferences +- `theme` (string) - UI theme: 'light', 'dark', or 'auto' +- `sidebar_collapsed` (boolean) - Sidebar collapsed state + +### Notification Preferences +- `email_notifications_enabled` (boolean) - Email notifications enabled +- `browser_notifications_enabled` (boolean) - Browser notifications enabled +- `notification_acknowledgments` (string) - Comma-separated acknowledged notification IDs + +### Feature Preferences +- `beta_features_enabled` (boolean) - Beta features enabled + +## Frontend Integration + +The User Preferences System integrates seamlessly with frontend applications through the service layer and existing API endpoints. Frontend developers can access preferences through the established API patterns used throughout DeployStack. + +## Performance Considerations + +The system is optimized for performance with several key features: + +### Database Indexing +- Primary index on `user_id` for fast user lookups +- Composite index on `user_id, preference_key` for specific preference queries +- Index on `preference_key` for analytics queries +- Index on `updated_at` for temporal queries + +### Efficient Queries +- Single query to fetch all user preferences +- Batch updates for multiple preference changes +- Automatic type conversion between strings and native types + +## Type Safety Features + +The system provides comprehensive type safety: + +### Runtime Validation +All preference values are validated against the configuration schema before storage. + +### TypeScript Integration +Full TypeScript support with auto-completion and type checking: + +```typescript +// Type-safe preference access +const preferences: UserPreferences = await preferencesService.getUserPreferences(userId); + +// TypeScript knows the available keys and their types +const theme: string = preferences.theme || 'auto'; +const sidebarCollapsed: boolean = preferences.sidebar_collapsed || false; +``` + +### Configuration-Driven Types +Types are automatically derived from the configuration, ensuring consistency between defaults and usage. + +## Migration Strategy + +If you need to rename or remove preferences: + +### Renaming Preferences +1. Add the new preference to config with desired default +2. Create a migration script to copy old values to new keys +3. Remove the old preference from config after migration +4. Restart application + +### Removing Preferences +1. Remove from configuration file +2. Restart application +3. Old preference data remains in database but becomes inaccessible +4. Optionally clean up old data with a maintenance script + +## Development Workflow + +### Local Development +1. Add preference to config file +2. Restart development server +3. Test with new user registration (gets defaults automatically) +4. Test with existing users (gets defaults on first access) + +### Production Deployment +1. Deploy code changes with new preferences in config +2. Restart application servers +3. New preferences are immediately available +4. No database downtime or migration required + +## Related Documentation + +- [API Security](/development/backend/api-security) - Security patterns and authorization +- [Role Management](/development/backend/roles) - Permission system details +- [Database Schema](https://github.com/deploystackio/deploystack/blob/main/services/backend/src/db/schema.sqlite.ts) - Complete database schema reference + +## Key Benefits + +### For Developers +- **Zero Migration Workflow**: Add preferences without database changes +- **Type Safety**: Full TypeScript support with runtime validation +- **Simple API**: Intuitive service layer methods +- **Performance**: Optimized queries with proper indexing + +### For Operations +- **No Downtime**: Preference additions require no database migrations +- **Secure**: Permission-based access with proper isolation +- **Scalable**: Separate table architecture supports complex queries +- **Maintainable**: Config-driven approach with clear separation of concerns + +The User Preferences System represents a modern approach to user settings management, balancing flexibility with performance while maintaining the simplicity that DeployStack developers expect. diff --git a/docs/development/gateway/oauth.mdx b/docs/development/gateway/oauth.mdx index 7653e0d..fc807d1 100644 --- a/docs/development/gateway/oauth.mdx +++ b/docs/development/gateway/oauth.mdx @@ -35,7 +35,7 @@ The authorization URL includes: - `response_type=code` for authorization code flow - `client_id=deploystack-gateway-cli` for client identification - `redirect_uri=http://localhost:8976/oauth/callback` for callback handling -- Requested scopes: `mcp:read account:read user:read teams:read offline_access` +- Requested scopes (see [OAuth Scope Management](#oauth-scope-management) below) - PKCE parameters: `code_challenge` and `code_challenge_method=S256` - Random `state` parameter for security @@ -90,7 +90,7 @@ The gateway is pre-registered with the backend as: - **Client ID**: `deploystack-gateway-cli` - **Client Type**: Public client (no secret required) - **Redirect URIs**: `http://localhost:8976/oauth/callback`, `http://127.0.0.1:8976/oauth/callback` -- **Allowed Scopes**: `mcp:read`, `account:read`, `user:read`, `teams:read`, `offline_access` +- **Allowed Scopes**: See source code at `services/gateway/src/utils/auth-config.ts` - **PKCE**: Required with SHA256 method - **Token Lifetime**: 1 week access tokens, 30 day refresh tokens @@ -212,6 +212,50 @@ The OAuth implementation follows security best practices: For detailed security implementation including credential storage, token expiration, and local file security, see the [Gateway Security Guide](/development/gateway/security). +## OAuth Scope Management + +The gateway requests specific OAuth scopes during authentication to access backend APIs. Scope configuration must stay synchronized between the gateway and backend. + +### Current Scopes + +For the current list of supported scopes, check the source code at: +- **Gateway scopes**: `services/gateway/src/utils/auth-config.ts` in the `scopes` array +- **Backend validation**: `services/backend/src/services/oauth/authorizationService.ts` in the `validateScope()` method + +### Adding New Scopes + +When the backend adds support for a new OAuth scope, you must update the gateway configuration: + +1. **Add the scope** to the `scopes` array in `services/gateway/src/utils/auth-config.ts` +2. **Add a description** to the `SCOPE_DESCRIPTIONS` object in the same file +3. **Test the login flow** to ensure the new scope is requested and granted + +Example: +```typescript +// In services/gateway/src/utils/auth-config.ts +scopes: [ + 'mcp:read', + 'mcp:categories:read', + 'your-new-scope', // Add new scope here + // ... other scopes +], + +// And add description +export const SCOPE_DESCRIPTIONS: Record = { + 'mcp:read': 'Access your MCP server installations and configurations', + 'your-new-scope': 'Description of what this scope allows', // Add description + // ... other descriptions +}; +``` + +### Scope Synchronization + +**Critical**: The gateway and backend must have matching scope configurations: +- If backend supports a scope but gateway doesn't request it, users won't get that permission +- If gateway requests a scope but backend doesn't support it, authentication will fail + +Always coordinate scope changes between both services. + ## Testing OAuth Flow During development, the OAuth flow can be tested: diff --git a/docs/meta.json b/docs/meta.json index 4119dba..d4354a1 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -33,6 +33,6 @@ "development/index", "development/frontend", "development/backend", - "development/gateway" + "development/gateway" ] } \ No newline at end of file diff --git a/docs/teams.mdx b/docs/teams.mdx index 7fbe20a..34d8846 100644 --- a/docs/teams.mdx +++ b/docs/teams.mdx @@ -36,7 +36,7 @@ This default team is immediately ready for use - you can start configuring MCP s ### Additional Teams -You can create additional teams beyond your automatically created default team to organize different projects or environments. +You can create additional teams beyond your automatically created default team to organize different projects or environments. The team creation limit can be adjusted by administrators in Global Settings (default: 3 teams maximum). ## What Teams Contain @@ -177,6 +177,7 @@ Team administrators can add new members to their teams (except default teams): **Limitations**: - **Default teams**: Cannot add members to your personal default team - **Existing users only**: Can only add users who already have DeployStack accounts +- **Member limits**: Non-default teams have a configurable member limit (adjustable in Global Settings, default: 3 members) ### Managing Member Roles