From fe78443215b45f7605e1dafdc214c4ebcb36dc0c Mon Sep 17 00:00:00 2001 From: Lasim Date: Sat, 9 Aug 2025 12:08:51 +0200 Subject: [PATCH 1/4] refactor(sitemap): simplify page retrieval and enhance sitemap entry mapping --- app/sitemap.ts | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/app/sitemap.ts b/app/sitemap.ts index 526d33b..cdecdaa 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -3,28 +3,17 @@ import { source } from '@/lib/source'; export const dynamic = 'force-static'; -function getPages(tree: any): any[] { - const pages: any[] = []; - - function traverse(nodes: any[]) { - for (const node of nodes) { - if (node.type === 'page') { - pages.push(node); - } else if ('children' in node) { - traverse(node.children); - } - } - } - - traverse(tree.children); - return pages; -} - export default function sitemap(): MetadataRoute.Sitemap { - const pages = getPages(source.pageTree).map((page) => ({ + // Get all pages from source.getPages() which combines all sources + const allPages = source.getPages(); + + // Map pages to sitemap entries + const sitemapEntries = allPages.map((page) => ({ url: new URL(page.url, 'https://docs.deploystack.io').toString(), - lastModified: page.lastModified, + lastModified: page.data.lastModified ? new Date(page.data.lastModified) : undefined, + changeFrequency: 'weekly' as const, + priority: page.url === '/' ? 1.0 : page.url.startsWith('/development') || page.url.startsWith('/self-hosted') ? 0.7 : 0.8, })); - return pages; + return sitemapEntries; } From 3acb9a38d4cb8501ffb3a23d164d10ae1de6e783 Mon Sep 17 00:00:00 2001 From: Lasim Date: Sat, 9 Aug 2025 16:27:17 +0200 Subject: [PATCH 2/4] feat: update configuration and add verification files - Modified .gitignore to exclude additional file types - Updated next.config.mjs to disable trailing slashes and prevent redirects - Added postbuild script in package.json for serving JSON - Added serve.json configuration for clean URLs and directory listing --- .gitignore | 4 ++++ next.config.mjs | 3 ++- package.json | 1 + public/google99cd6dc986babdb3.html | 1 + serve.json | 5 +++++ 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 public/google99cd6dc986babdb3.html create mode 100644 serve.json diff --git a/.gitignore b/.gitignore index eece690..3802fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,8 @@ coverage/ ._*.tsx ._*.css ._*.cjs +._*.mjs +._*.js +._*.html +._*.scss out/ diff --git a/next.config.mjs b/next.config.mjs index 107a230..e267f5a 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,7 +6,8 @@ const withMDX = createMDX(); const config = { reactStrictMode: true, output: 'export', // Enable static HTML export - trailingSlash: true, // Required for static export + trailingSlash: false, // Changed to false to avoid trailing slashes + skipTrailingSlashRedirect: true, // Prevent automatic trailing slash redirects // basePath: '/docs', // Set base path for the application // assetPrefix: '/docs', // Ensure assets are also prefixed with /docs images: { diff --git a/package.json b/package.json index c8e983f..c06c784 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "next dev", "build": "next build", "start": "npx serve@latest out", + "postbuild": "cp serve.json out/serve.json", "lint:md": "npx markdownlint-cli2 '**/*.md' '#node_modules' '#.github'", "lint:links": "node check-links.js", "semantic-release": "semantic-release", diff --git a/public/google99cd6dc986babdb3.html b/public/google99cd6dc986babdb3.html new file mode 100644 index 0000000..e41c371 --- /dev/null +++ b/public/google99cd6dc986babdb3.html @@ -0,0 +1 @@ +google-site-verification: google99cd6dc986babdb3.html \ No newline at end of file diff --git a/serve.json b/serve.json new file mode 100644 index 0000000..c080f56 --- /dev/null +++ b/serve.json @@ -0,0 +1,5 @@ +{ + "cleanUrls": true, + "trailingSlash": false, + "directoryListing": false +} From 7a13fa6f07a3420686b3ad893cd5e3e2da54108d Mon Sep 17 00:00:00 2001 From: Lasim Date: Sat, 9 Aug 2025 17:07:07 +0200 Subject: [PATCH 3/4] Add OAuth Provider Implementation documentation and remove old OAuth guide - Introduced a new document detailing the implementation of third-party OAuth providers for user authentication in DeployStack. - Removed the outdated OAuth Implementation Guide to streamline documentation. - Added a comprehensive guide for the OAuth2 server implementation, detailing authorization flows, token management, and security practices. - Updated the index page to reflect the new documentation structure. --- app/[[...slug]]/page.tsx | 1 - docs/development/backend/auth.mdx | 274 ++++++- docs/development/backend/oauth-providers.mdx | 285 +++++++ docs/development/backend/oauth.mdx | 736 ------------------- docs/development/backend/oauth2-server.mdx | 343 +++++++++ docs/index.mdx | 3 +- 6 files changed, 900 insertions(+), 742 deletions(-) create mode 100644 docs/development/backend/oauth-providers.mdx delete mode 100644 docs/development/backend/oauth.mdx create mode 100644 docs/development/backend/oauth2-server.mdx diff --git a/app/[[...slug]]/page.tsx b/app/[[...slug]]/page.tsx index 37c3619..1c390be 100644 --- a/app/[[...slug]]/page.tsx +++ b/app/[[...slug]]/page.tsx @@ -9,7 +9,6 @@ import { getFinalPageTitle } from '@/lib/h1-extractor'; import { readFile } from 'fs/promises'; import { getMDXComponents } from '@/mdx-components'; import { homeOptions, docsOptions } from '../layout.config'; -import { docs } from '@/.source/index'; export default async function Page({ params, diff --git a/docs/development/backend/auth.mdx b/docs/development/backend/auth.mdx index 182f065..8cdd1ba 100644 --- a/docs/development/backend/auth.mdx +++ b/docs/development/backend/auth.mdx @@ -1,6 +1,274 @@ --- -title: Authentication System -description: Complete guide to implementing user authentication in DeployStack Backend. +title: Backend Authentication System +description: Technical documentation for the DeployStack backend authentication implementation, including session management, password hashing, and authentication flows. --- -# DeployStack Authentication System \ No newline at end of file +# Backend Authentication System + +This document provides technical details about the DeployStack backend authentication system implementation. For user-facing authentication configuration, see [Authentication Methods](/auth). For OAuth provider implementation details, see [OAuth Provider Implementation](/development/backend/oauth-providers) and [OAuth2 Server Implementation](/development/backend/oauth2-server). + +## Architecture Overview + +The backend authentication system is built on several key components: + +- **[Lucia v3](https://lucia-auth.com/)** - Core session management and authentication library +- **[Argon2](https://github.com/napi-rs/node-rs)** - Industry-standard password hashing +- **[Arctic](https://arctic.js.org/)** - OAuth 2.0 client library for provider integration +- **Database-backed sessions** - SQLite/Turso storage for session persistence +- **Dual authentication** - Support for both cookie sessions and OAuth2 Bearer tokens + +## Authentication Flow Types + +The backend supports multiple authentication flows to accommodate different client types and use cases: + +### 1. Email/Password Authentication + +Traditional username/password authentication with email verification: + +- **Registration** (`/api/auth/email/register`) - Creates new user accounts +- **Login** (`/api/auth/email/login`) - Authenticates existing users +- **Email Verification** - Optional verification requirement based on global settings +- **Password Reset** - Secure token-based password recovery + +### 2. OAuth Provider Authentication + +Third-party authentication via OAuth providers (currently GitHub): + +- **Provider Login** (`/api/auth/github/login`) - Initiates OAuth flow +- **OAuth Callback** (`/api/auth/github/callback`) - Handles provider response +- **Account Linking** - Automatically links OAuth accounts to existing users by email +- **User Provisioning** - Creates new users from OAuth profile data + +### 3. OAuth2 Server Authentication + +Bearer token authentication for programmatic API access: + +- **Authorization** (`/api/oauth2/auth`) - OAuth2 authorization endpoint +- **Token Exchange** (`/api/oauth2/token`) - Exchanges codes for access tokens +- **Bearer Authentication** - API access using Authorization header +- **Scope-based Permissions** - Fine-grained access control + +## Core Components + +### Session Management + +Sessions are managed through Lucia v3 with database persistence: + +#### Session Storage + +Sessions are stored in the `authSession` table with the following structure: +- **Session ID**: 40-character cryptographically random identifier +- **User ID**: Foreign key reference to the authenticated user +- **Expiration**: 30-day lifetime from creation +- **Cookie Attributes**: httpOnly, secure (production), sameSite (lax) + +#### Session Lifecycle + +1. **Creation**: Generated after successful authentication +2. **Validation**: Checked on each request via `authHook` +3. **Refresh**: Sessions are not automatically extended +4. **Expiration**: Expired sessions are deleted on validation attempt +5. **Logout**: Explicit session deletion + +### Password Security + +Passwords are secured using Argon2id with carefully chosen parameters: + +#### Hashing Parameters +- **Algorithm**: Argon2id (resistant to side-channel and GPU attacks) +- **Memory Cost**: 19456 KB (19 MB) +- **Time Cost**: 2 iterations +- **Parallelism**: 1 thread +- **Output Length**: 32 bytes +- **Salt**: Unique per password, automatically generated + +#### Password Verification + +The verification process uses constant-time comparison to prevent timing attacks: +1. Extract stored hash and salt from database +2. Re-compute hash with provided password +3. Compare hashes using constant-time algorithm +4. Return authentication result + +### Authentication Hooks + +The backend uses Fastify hooks for request-level authentication: + +#### authHook (Global) + +Runs on every request to establish authentication context: +- Reads session cookie if present +- Validates session against database +- Populates `request.user` and `request.session` +- Handles expired session cleanup +- Skips authentication if database not ready + +#### requireAuthHook (Route-specific) + +Enforces authentication on protected routes: +- Checks for valid user and session +- Returns 401 Unauthorized if not authenticated +- Used as preValidation hook on protected endpoints + +## User Registration Flow + +### Email Registration Process + +1. **Validation**: Input validation using Zod schemas +2. **Uniqueness Check**: Verify username and email availability +3. **Password Hashing**: Secure hash generation with Argon2 +4. **User Creation**: Database insertion with role assignment +5. **First User Logic**: Automatic global_admin role for first user +6. **Email Verification**: Send verification email (if enabled) +7. **Team Creation**: Automatic default team creation +8. **Session Creation**: Immediate login after registration +9. **Response**: User data and success message + +### Role Assignment + +- **First User**: Automatically assigned `global_admin` role +- **Subsequent Users**: Assigned `global_user` role +- **OAuth Users**: Always assigned `global_user` role +- **Email Verification**: First user auto-verified, others depend on settings + +## Login Authentication Flow + +### Email Login Process + +1. **Global Check**: Verify login is enabled in settings +2. **User Lookup**: Find user by email or username +3. **Password Verification**: Argon2 hash comparison +4. **Email Verification Check**: Ensure email is verified (if required) +5. **Session Creation**: Generate new 30-day session +6. **Cookie Setting**: Set httpOnly session cookie +7. **Response**: User data and session established + +### Authentication State + +After successful login, the following state is established: +- **Session Cookie**: Contains session ID +- **Database Session**: Active session record +- **User Context**: Available in `request.user` +- **Session Context**: Available in `request.session` + +## Email Verification System + +### Verification Requirements + +- **Controlled by**: `global.send_mail` setting +- **First User**: Always auto-verified for system access +- **Email Users**: Must verify before login (when enabled) +- **OAuth Users**: Auto-verified (provider emails trusted) + +### Verification Token Flow + +1. **Token Generation**: 32-character random token +2. **Token Storage**: Hashed with Argon2 (same as passwords) +3. **Email Dispatch**: Verification link sent via SMTP +4. **Token Validation**: Constant-time comparison +5. **Account Activation**: Email marked as verified +6. **Token Cleanup**: Single-use, expires after 24 hours + +## Password Reset Flow + +### Reset Process + +1. **Request Initiation**: User provides email address +2. **Token Generation**: Secure random reset token +3. **Token Storage**: Hashed and stored with expiration +4. **Email Notification**: Reset link sent to user +5. **Token Validation**: Verify token and expiration +6. **Password Update**: New password hashed and stored +7. **Token Invalidation**: Used tokens are deleted +8. **Session Creation**: Optional auto-login after reset + +### Security Measures + +- **Rate Limiting**: Prevent brute force attempts +- **Token Expiration**: 1-hour validity window +- **Single Use**: Tokens invalidated after use +- **User Notification**: Email sent on password change + +## Dual Authentication Support + +The backend supports both cookie-based and Bearer token authentication simultaneously: + +### Cookie Authentication + +- **Primary Use**: Web application sessions +- **Validation**: Via `authHook` on every request +- **Storage**: Server-side session with cookie identifier +- **Lifetime**: 30-day expiration + +### Bearer Token Authentication + +- **Primary Use**: CLI and API access +- **Validation**: Via `oauthMiddleware` +- **Format**: OAuth2 access tokens +- **Lifetime**: 1-hour access, 30-day refresh + +### Middleware Integration + +Endpoints can accept either authentication method using `requireAuthenticationAny()`: +- First checks for cookie session +- Falls back to Bearer token validation +- Populates same `request.user` interface +- Maintains authentication type in context + +## Security Best Practices + +### Implementation Security + +1. **Password Storage**: Never store plaintext, always use Argon2 +2. **Session Management**: Cryptographically secure IDs, proper expiration +3. **Token Security**: Constant-time comparisons, secure random generation +4. **HTTPS Enforcement**: Secure cookies in production +5. **CSRF Protection**: State parameters in OAuth, sameSite cookies + +### Validation and Sanitization + +- **Input Validation**: All inputs validated with Zod schemas +- **Email Normalization**: Lowercase conversion before storage +- **SQL Injection Prevention**: Parameterized queries via Drizzle ORM +- **XSS Prevention**: httpOnly cookies, no client-side session access + +## Database Schema + +The authentication system uses the following core tables: + +### authUser Table + +Stores user account information: +- User identification (id, username, email) +- Authentication data (hashed_password, auth_type) +- Profile information (first_name, last_name) +- OAuth provider IDs (github_id) +- Account status (email_verified, role_id) + +### authSession Table + +Manages active user sessions: +- Session identification (id) +- User association (user_id) +- Expiration tracking (expires_at) + +### Additional Tables + +Supporting tables for full functionality: +- **email_verification_tokens**: Email verification tokens +- **password_reset_tokens**: Password reset tokens +- **oauth_authorization_codes**: OAuth2 authorization codes +- **oauth_access_tokens**: OAuth2 access tokens +- **oauth_refresh_tokens**: OAuth2 refresh tokens + +## API Reference + +For a complete list of authentication API endpoints and their specifications, see the [Backend API Documentation](/development/backend/api). The API documentation includes OpenAPI specifications with detailed request/response schemas for all authentication endpoints. + +## Related Documentation + +- [Security Policy](/development/backend/security) - Security implementation details +- [Database Schema](/development/backend/database) - Complete database structure +- [API Documentation](/development/backend/api) - Full API reference +- [OAuth Provider Implementation](/development/backend/oauth-providers) - Third-party OAuth login setup +- [OAuth2 Server Implementation](/development/backend/oauth2-server) - OAuth2 server for API access diff --git a/docs/development/backend/oauth-providers.mdx b/docs/development/backend/oauth-providers.mdx new file mode 100644 index 0000000..d771638 --- /dev/null +++ b/docs/development/backend/oauth-providers.mdx @@ -0,0 +1,285 @@ +--- +title: OAuth Provider Implementation +description: Developer guide for implementing third-party OAuth providers (GitHub, Google, etc.) for user authentication in DeployStack +--- + +# OAuth Provider Implementation + +This document describes how to implement third-party OAuth providers for user authentication in DeployStack. The system currently supports GitHub OAuth as a reference implementation, but the architecture is designed to easily accommodate additional providers like Google, Microsoft, GitLab, and others. + +For general authentication architecture, see [Backend Authentication System](/development/backend/auth). For OAuth2 server implementation (API access), see [OAuth2 Server Implementation](/development/backend/oauth2-server). + +## Overview + +OAuth provider integration allows users to authenticate using their existing accounts from third-party services. This provides a seamless login experience without requiring users to create and manage separate DeployStack credentials. + +## Architecture + +The OAuth provider system is built on: + +- **[Arctic](https://arctic.js.org/)** - OAuth 2.0 client library supporting multiple providers +- **Global Settings** - Database-driven configuration for each provider +- **Modular Routes** - Separate route files for each provider +- **Unified Session Management** - Integration with Lucia authentication +- **Account Linking** - Automatic linking based on email addresses + +## File Structure + +OAuth providers follow a consistent file organization pattern: + +``` +services/backend/src/ +├── routes/auth/ +│ ├── github.ts # GitHub OAuth routes +│ ├── githubStatus.ts # GitHub status endpoint +│ ├── [provider].ts # New provider routes +│ └── [provider]Status.ts # New provider status +├── global-settings/ +│ ├── github-oauth.ts # GitHub settings +│ └── [provider]-oauth.ts # New provider settings +├── db/ +│ └── schema.sqlite.ts # User table with provider IDs +└── lib/ + └── lucia.ts # Session management +``` + +## Current Implementation: GitHub + +GitHub OAuth serves as the reference implementation demonstrating the complete flow: + +### OAuth Flow + +1. **Login Initiation** (`/api/auth/github/login`) + - Validates login is enabled globally + - Checks provider configuration + - Generates CSRF state parameter + - Redirects to GitHub authorization + +2. **User Authorization** (at GitHub) + - User reviews requested permissions + - Approves or denies access + - Redirects back to callback URL + +3. **Callback Processing** (`/api/auth/github/callback`) + - Validates state parameter + - Exchanges code for access token + - Fetches user profile information + - Creates or links user account + - Establishes session + +4. **Status Endpoint** (`/api/auth/github/status`) + - Reports if provider is enabled + - Indicates configuration status + - Used by frontend for UI decisions + +### User Provisioning + +When a user authenticates via OAuth: + +- **New Users**: Account created with provider profile data +- **Existing Users**: Account linked if email matches +- **Role Assignment**: Always `global_user` (never admin) +- **Team Creation**: Default team created automatically +- **Email Trust**: Provider emails considered verified + +### Security Features + +- **State Parameter**: CSRF protection +- **First User Protection**: Must use email registration +- **Secure Cookies**: httpOnly, secure in production +- **Minimal Scopes**: Only request necessary permissions + +## Adding a New OAuth Provider + +Follow these steps to add support for a new OAuth provider: + +### Step 1: Verify Arctic Support + +Check if Arctic supports your provider: + +```bash +# Arctic supports many providers out of the box +# See: https://arctic.js.org/providers +``` + +Supported providers include: +- Google +- Microsoft/Azure AD +- GitLab +- Discord +- Apple +- And many more... + +### Step 2: Create Global Settings + +Create settings file at `src/global-settings/[provider]-oauth.ts`: + +```typescript +// Example structure (not actual code to avoid outdating) +// Define settings for: +// - enabled (boolean) +// - clientId (string) +// - clientSecret (string, encrypted) +// - callbackUrl (string) +// - scope (string) +``` + +Key considerations: +- Mark `clientSecret` as `is_encrypted: true` +- Set appropriate default scopes +- Group settings under 'auth' category +- Add to global settings index + +### Step 3: Update Database Schema + +Add provider ID field to `authUser` table: + +```typescript +// In src/db/schema.sqlite.ts +// Add field like: +// google_id: text('google_id').unique() +// microsoft_id: text('microsoft_id').unique() +``` + +Generate migration after schema update: +```bash +npm run db:generate +``` + +### Step 4: Implement OAuth Routes + +Create route files following the pattern: + +#### Login Route (`src/routes/auth/[provider].ts`) + +Implements `/api/auth/[provider]/login`: +- Check if login and provider are enabled +- Generate state for CSRF protection +- Create authorization URL with Arctic +- Store state in secure cookie +- Redirect to provider + +#### Callback Route (`src/routes/auth/[provider].ts`) + +Implements `/api/auth/[provider]/callback`: +- Validate state parameter +- Exchange code for tokens +- Fetch user information +- Handle user creation/linking +- Create session +- Redirect to frontend + +#### Status Route (`src/routes/auth/[provider]Status.ts`) + +Implements `/api/auth/[provider]/status`: +- Check if provider is enabled +- Verify configuration is complete +- Return status for frontend + +### Step 5: Register Routes + +Add routes to authentication router: + +```typescript +// In src/routes/auth/index.ts +// Import and register: +// await fastify.register([provider]AuthRoutes, { prefix: '/[provider]' }) +// await fastify.register([provider]StatusRoutes, { prefix: '/[provider]' }) +``` + +## Provider-Specific Considerations + +Different providers have unique requirements and behaviors: + +### Google OAuth + +- **User Info Endpoint**: `https://www.googleapis.com/oauth2/v2/userinfo` +- **Recommended Scopes**: `openid email profile` +- **Email Field**: Always available in response +- **Name Fields**: `given_name` and `family_name` + +### Microsoft OAuth + +- **User Info Endpoint**: `https://graph.microsoft.com/v1.0/me` +- **Recommended Scopes**: `openid email profile` or `User.Read` +- **Email Field**: Available as `mail` or `userPrincipalName` +- **Tenant Support**: Consider multi-tenant vs single-tenant + +### GitLab OAuth + +- **User Info Endpoint**: Instance-specific (self-hosted) +- **Recommended Scopes**: `read_user` +- **Instance URL**: Configurable for self-hosted +- **Username Field**: Available as `username` + +### Discord OAuth + +- **User Info Endpoint**: `https://discord.com/api/users/@me` +- **Recommended Scopes**: `identify email` +- **Email Verification**: Check `verified` field +- **Username Format**: `username#discriminator` + +## Common Implementation Patterns + +### User Information Extraction + +Each provider returns user data differently: + +1. **Email Address**: Primary identifier for account linking +2. **Username**: Generate from email if not provided +3. **Display Name**: Parse from full name or use username +4. **Provider ID**: Unique identifier from provider + +### Account Linking Logic + +Consistent approach across providers: + +1. Check for existing user with provider ID +2. If not found, check for user with same email +3. Link accounts if email matches +4. Create new account if no match +5. Prevent first user creation via OAuth + +## Configuration Management + +### Global Settings Integration + +Each provider needs configuration in global settings: + +- **Enable Toggle**: Allow administrators to enable/disable +- **Credentials**: Client ID and encrypted secret +- **Callback URL**: Must match provider configuration +- **Scopes**: Permissions requested from provider + +### Frontend Integration + +Frontend checks provider status to show login buttons: + +1. Call `/api/auth/[provider]/status` endpoint +2. Show button only if enabled and configured +3. Handle login flow and redirects +4. Display appropriate error messages + +## Security Best Practices + +### Implementation Security + +1. **State Validation**: Always validate CSRF state parameter +2. **Secure Storage**: Encrypt client secrets in database +4. **Scope Minimization**: Request only necessary permissions +5. **Token Handling**: Never store provider tokens long-term + +### User Security + +1. **Email Verification**: Trust provider-verified emails +2. **Account Linking**: Prevent unauthorized linking +3. **Role Restrictions**: OAuth users never get admin role +4. **First User Protection**: Require email for first user + +## Related Documentation + +- [Backend Authentication System](/development/backend/auth) - Core authentication architecture +- [OAuth2 Server Implementation](/development/backend/oauth2-server) - OAuth2 server for API access +- [Security Policy](/development/backend/security) - Security implementation details +- [Global Settings](/development/backend/global-settings) - Configuration management +- [GitHub OAuth Setup](/github-oauth-setup) - User-facing GitHub OAuth setup guide diff --git a/docs/development/backend/oauth.mdx b/docs/development/backend/oauth.mdx deleted file mode 100644 index 8304b0b..0000000 --- a/docs/development/backend/oauth.mdx +++ /dev/null @@ -1,736 +0,0 @@ ---- -title: OAuth Implementation Guide -description: Developer guide for implementing OAuth providers and OAuth2 server in DeployStack ---- - -# OAuth Implementation Guide - -This guide explains how to implement OAuth providers and the OAuth2 server in DeployStack's backend. The system supports both OAuth provider integration (GitHub, Google, etc.) and OAuth2 server functionality for API access. - -## Architecture Overview - -DeployStack implements two distinct OAuth systems: - -### 1. OAuth Provider Integration (Social Login) -For user authentication via third-party providers: -- **[Arctic](https://arctic.js.org/)** - OAuth 2.0 client library for various providers -- **[Lucia](https://lucia-auth.com/)** - Authentication library for session management -- **Global Settings** - Database-driven configuration for OAuth providers - -### 2. OAuth2 Server (API Access) -For programmatic API access by CLI tools and applications: -- **RFC 6749 compliant** OAuth2 authorization server -- **PKCE support** (RFC 7636) for enhanced security -- **Scope-based access control** for fine-grained permissions -- **Dual authentication** supporting both cookies and Bearer tokens - -## OAuth2 Server Implementation - -The OAuth2 server enables CLI tools and applications to access DeployStack APIs securely using Bearer tokens. - -For OAuth2 security details including PKCE, token security, authorization flow security, and Bearer token authentication, see the [Security Policy](/development/backend/security#oauth2-server-security). - -### Database Schema - -Three tables support OAuth2 functionality: - -```sql --- Authorization codes (short-lived, exchanged for tokens) -CREATE TABLE oauth_authorization_codes ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - client_id TEXT NOT NULL, - redirect_uri TEXT NOT NULL, - scope TEXT NOT NULL, - state TEXT NOT NULL, - code_challenge TEXT NOT NULL, - code_challenge_method TEXT NOT NULL, - expires_at INTEGER NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES authUser(id) -); - --- Access tokens (1-hour lifetime) -CREATE TABLE oauth_access_tokens ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - client_id TEXT NOT NULL, - scope TEXT NOT NULL, - token_hash TEXT NOT NULL, - expires_at INTEGER NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES authUser(id) -); - --- Refresh tokens (30-day lifetime) -CREATE TABLE oauth_refresh_tokens ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - client_id TEXT NOT NULL, - token_hash TEXT NOT NULL, - expires_at INTEGER NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES authUser(id) -); -``` - -### OAuth2 Services - -#### AuthorizationService -Handles OAuth2 authorization flow with PKCE validation: - -```typescript -// services/backend/src/services/oauth/authorizationService.ts -export class AuthorizationService { - static validateClient(clientId: string): boolean - static validateRedirectUri(redirectUri: string): boolean - static validateScope(scope: string): boolean - static async storeAuthorizationRequest(...) - static async getAuthorizationRequest(requestId: string) - static async generateAuthorizationCode(requestId: string) - static async verifyAuthorizationCode(code: string, codeVerifier: string, ...) -} -``` - -#### TokenService -Manages access and refresh tokens: - -```typescript -// services/backend/src/services/oauth/tokenService.ts -export class TokenService { - static async generateAccessToken(userId: string, scope: string, clientId: string) - static async generateRefreshToken(userId: string, clientId: string) - static async verifyAccessToken(token: string) - static async refreshAccessToken(refreshToken: string, clientId: string) - static async revokeToken(tokenId: string) -} -``` - -#### OAuthCleanupService -Automatic cleanup of expired tokens (runs every hour): - -```typescript -// services/backend/src/services/oauth/cleanupService.ts -export class OAuthCleanupService { - static startCleanupScheduler(): void - static async cleanupExpiredTokens(): Promise -} -``` - -### OAuth2 Endpoints - -#### Authorization Endpoint -```typescript -GET /api/oauth2/auth -``` -Initiates OAuth2 flow with PKCE. Redirects to consent page for user authorization. - -**Parameters:** -- `response_type=code` (required) -- `client_id` (required) -- `redirect_uri` (required) -- `scope` (required) -- `state` (required) -- `code_challenge` (required) -- `code_challenge_method=S256` (required) - -#### Consent Endpoints -```typescript -GET /api/oauth2/consent?request_id= -POST /api/oauth2/consent -``` -Professional HTML consent page with security warnings and scope descriptions. - -#### Token Endpoint -```typescript -POST /api/oauth2/token -``` -Exchanges authorization code for access token or refreshes tokens. - -**Grant Types:** -- `authorization_code` - Exchange code for tokens -- `refresh_token` - Refresh access token - -### OAuth2 Scopes - -Available scopes for fine-grained access control: - -- `mcp:read` - Read MCP server installations and configurations -- `account:read` - Read account information -- `user:read` - Read user profile information -- `teams:read` - Read team memberships and information -- `offline_access` - Maintain access when not actively using the application - -### Dual Authentication Middleware - -Enable both cookie and OAuth2 authentication on endpoints: - -```typescript -import { requireAuthenticationAny, requireOAuthScope } from '../../middleware/oauthMiddleware'; - -fastify.get('/your-endpoint', { - schema: { - security: [ - { cookieAuth: [] }, // Cookie authentication - { bearerAuth: [] } // OAuth2 Bearer token - ] - }, - preValidation: [ - requireAuthenticationAny(), // Accept either auth method - requireOAuthScope('your:scope') // Enforce OAuth2 scope - ] -}, async (request, reply) => { - // Endpoint accessible via both authentication methods - const authType = request.tokenPayload ? 'oauth2' : 'cookie'; - const userId = request.user!.id; -}); -``` - -### Client Configuration - -**DeployStack Gateway CLI:** -- **Client ID**: `deploystack-gateway-cli` -- **Redirect URIs**: `http://localhost:8976/oauth/callback`, `http://127.0.0.1:8976/oauth/callback` -- **PKCE**: Required (SHA256 method) -- **Token Lifetime**: 1 hour access tokens, 30 day refresh tokens - -### OAuth2 Flow Example - -```bash -# 1. Authorization Request (redirects to consent page) -curl -b cookies.txt "http://localhost:3000/api/oauth2/auth?response_type=code&client_id=deploystack-gateway-cli&redirect_uri=http://localhost:8976/oauth/callback&scope=mcp:read%20teams:read&state=xyz&code_challenge=abc&code_challenge_method=S256" - -# 2. User approves on consent page, receives authorization code - -# 3. Token Exchange -curl -X POST "http://localhost:3000/api/oauth2/token" \ - -H "Content-Type: application/json" \ - -d '{ - "grant_type": "authorization_code", - "code": "auth_code_here", - "redirect_uri": "http://localhost:8976/oauth/callback", - "client_id": "deploystack-gateway-cli", - "code_verifier": "verifier_here" - }' - -# 4. API Access with Bearer Token -curl -H "Authorization: Bearer " \ - "http://localhost:3000/api/teams/me/default" -``` - -## OAuth Provider Integration (Social Login) - -The GitHub OAuth implementation serves as a reference for adding other providers. - -### File Structure - -``` -services/backend/src/ -├── routes/auth/ -│ ├── github.ts # GitHub OAuth routes -│ ├── githubStatus.ts # GitHub OAuth status endpoint -│ └── schemas.ts # OAuth validation schemas -├── global-settings/ -│ └── github-oauth.ts # GitHub OAuth global settings -└── lib/ - └── lucia.ts # Lucia authentication setup -``` - -## Adding a New OAuth Provider - -Follow these steps to add a new OAuth provider (e.g., Google): - -### 1. Install Provider Support - -First, ensure Arctic supports your provider: - -```bash -# Arctic supports many providers out of the box -# Check: https://arctic.js.org/providers -``` - -### 2. Create Global Settings - -Create a new global settings file for your provider: - -```typescript -// services/backend/src/global-settings/google-oauth.ts -import { z } from 'zod'; -import type { GlobalSettingDefinition } from './types'; - -export const GoogleOAuthSettingsSchema = z.object({ - enabled: z.boolean().default(false), - clientId: z.string().min(1, 'Client ID is required'), - clientSecret: z.string().min(1, 'Client Secret is required'), - callbackUrl: z.string().url('Must be a valid URL'), - scope: z.string().default('openid email profile'), -}); - -export type GoogleOAuthSettings = z.infer; - -export const googleOAuthSettings: GlobalSettingDefinition[] = [ - { - key: 'google_oauth_enabled', - type: 'boolean', - defaultValue: 'false', - description: 'Enable Google OAuth authentication', - group_id: 'auth', - }, - { - key: 'google_oauth_client_id', - type: 'string', - defaultValue: '', - description: 'Google OAuth Client ID', - group_id: 'auth', - }, - { - key: 'google_oauth_client_secret', - type: 'string', - defaultValue: '', - description: 'Google OAuth Client Secret', - group_id: 'auth', - is_encrypted: true, - }, - { - key: 'google_oauth_callback_url', - type: 'string', - defaultValue: 'http://localhost:3000/api/auth/google/callback', - description: 'Google OAuth callback URL', - group_id: 'auth', - }, - { - key: 'google_oauth_scope', - type: 'string', - defaultValue: 'openid email profile', - description: 'Google OAuth scopes (comma-separated)', - group_id: 'auth', - }, -]; -``` - -### 3. Add Provider to Global Settings Index - -Update the global settings index: - -```typescript -// services/backend/src/global-settings/index.ts -import { googleOAuthSettings } from './google-oauth'; - -// Add to the settings array -export const allGlobalSettings = [ - ...existingSettings, - ...googleOAuthSettings, -]; - -// Add helper function -export async function getGoogleOAuthConfiguration(): Promise { - const enabled = await getSetting('google_oauth_enabled'); - if (enabled !== 'true') return null; - - const clientId = await getSetting('google_oauth_client_id'); - const clientSecret = await getSetting('google_oauth_client_secret'); - const callbackUrl = await getSetting('google_oauth_callback_url'); - const scope = await getSetting('google_oauth_scope'); - - if (!clientId || !clientSecret) return null; - - return GoogleOAuthSettingsSchema.parse({ - enabled: true, - clientId, - clientSecret, - callbackUrl, - scope, - }); -} -``` - -### 4. Create OAuth Routes - -Create the OAuth routes file: - -```typescript -// services/backend/src/routes/auth/google.ts -import type { FastifyInstance, FastifyReply } from 'fastify'; -import { z } from 'zod'; -import { createSchema } from 'zod-openapi'; -import { getLucia } from '../../lib/lucia'; -import { getDb, getSchema } from '../../db'; -import { eq } from 'drizzle-orm'; -import { generateId } from 'lucia'; -import { generateState } from 'arctic'; -import { GlobalSettingsInitService } from '../../global-settings'; - -// Define callback schema -const GoogleCallbackSchema = z.object({ - code: z.string(), - state: z.string(), -}); - -type GoogleCallbackInput = z.infer; - -export default async function googleAuthRoutes(fastify: FastifyInstance) { - // Route to initiate Google login - fastify.get('/login', async (_request, reply: FastifyReply) => { - // Check if login is enabled - const isLoginEnabled = await GlobalSettingsInitService.isLoginEnabled(); - if (!isLoginEnabled) { - return reply.status(403).send({ - error: 'Login is currently disabled by administrator.' - }); - } - - // Check if Google OAuth is enabled and configured - const googleConfig = await GlobalSettingsInitService.getGoogleOAuthConfiguration(); - if (!googleConfig) { - return reply.status(403).send({ - error: 'Google OAuth is not enabled or not properly configured.' - }); - } - - const state = generateState(); - - // Create Google OAuth instance - const { Google } = await import('arctic'); - const googleAuth = new Google( - googleConfig.clientId, - googleConfig.clientSecret, - googleConfig.callbackUrl - ); - - const scopes = googleConfig.scope.split(',').map(s => s.trim()); - const url = await googleAuth.createAuthorizationURL(state, scopes); - - // Store state in cookie - reply.setCookie('oauth_state', state, { - path: '/', - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - maxAge: 60 * 10, // 10 minutes - sameSite: 'lax', - }); - - return reply.redirect(url.toString()); - }); - - // Route to handle Google callback - fastify.get<{ Querystring: GoogleCallbackInput }>('/callback', async (request, reply: FastifyReply) => { - // Validate state parameter - const storedState = request.cookies?.oauth_state; - const { code, state } = request.query; - - if (!storedState || !state || storedState !== state) { - return reply.status(400).send({ error: 'Invalid OAuth state.' }); - } - - // Clear state cookie - reply.setCookie('oauth_state', '', { maxAge: -1, path: '/' }); - - try { - const googleConfig = await GlobalSettingsInitService.getGoogleOAuthConfiguration(); - if (!googleConfig) { - return reply.status(403).send({ error: 'Google OAuth not configured.' }); - } - - // Create Google OAuth instance - const { Google } = await import('arctic'); - const googleAuth = new Google( - googleConfig.clientId, - googleConfig.clientSecret, - googleConfig.callbackUrl - ); - - // Exchange code for tokens - const tokens = await googleAuth.validateAuthorizationCode(code); - - // Fetch user information - const googleUserResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { - headers: { - Authorization: `Bearer ${tokens.accessToken()}` - } - }); - - if (!googleUserResponse.ok) { - return reply.status(400).send({ error: 'Failed to fetch Google user information.' }); - } - - const googleUser = await googleUserResponse.json(); - - // Extract user email - const userEmail = googleUser.email; - if (!userEmail) { - return reply.status(400).send({ error: 'Google email not available.' }); - } - - // Get database and schema - const db = getDb(); - const schema = getSchema(); - const authUserTable = schema.authUser; - - // Check if user already exists with this Google ID - const existingUser = await (db as any) - .select() - .from(authUserTable) - .where(eq(authUserTable.google_id, googleUser.id.toString())) - .limit(1); - - if (existingUser.length > 0) { - // Existing user - create session - const userId = existingUser[0].id; - const sessionId = generateId(40); - const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); - - const authSessionTable = schema.authSession; - await (db as any).insert(authSessionTable).values({ - id: sessionId, - user_id: userId, - expires_at: expiresAt.getTime() - }); - - const sessionCookie = getLucia().createSessionCookie(sessionId); - reply.setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - - const frontendUrl = await GlobalSettingsInitService.getPageUrl(); - return reply.redirect(frontendUrl); - } - - // Check for existing user by email - const userWithSameEmail = await (db as any) - .select() - .from(authUserTable) - .where(eq(authUserTable.email, userEmail.toLowerCase())) - .limit(1); - - if (userWithSameEmail.length > 0) { - // Link Google account to existing user - const existingUserId = userWithSameEmail[0].id; - await (db as any) - .update(authUserTable) - .set({ google_id: googleUser.id.toString() }) - .where(eq(authUserTable.id, existingUserId)); - - // Create session - const session = await getLucia().createSession(existingUserId, {}); - const sessionCookie = getLucia().createSessionCookie(session.id); - reply.setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - - const frontendUrl = await GlobalSettingsInitService.getPageUrl(); - return reply.redirect(frontendUrl); - } - - // Prevent first user creation via OAuth - const allUsers = await (db as any).select().from(authUserTable).limit(1); - if (allUsers.length === 0) { - return reply.status(403).send({ - error: 'The first user must be created via email registration.' - }); - } - - // Create new user - const newUserId = generateId(15); - const newUserData = { - id: newUserId, - username: googleUser.email.split('@')[0] || `google_user_${newUserId}`, - email: userEmail.toLowerCase(), - auth_type: 'google', - first_name: googleUser.given_name || null, - last_name: googleUser.family_name || null, - google_id: googleUser.id.toString(), - role_id: 'global_user', - email_verified: true, - }; - - await (db as any).insert(authUserTable).values(newUserData); - - // Create default team - try { - const { TeamService } = await import('../../services/teamService'); - await TeamService.createDefaultTeamForUser(newUserId, newUserData.username); - } catch (teamError) { - // Don't fail login if team creation fails - } - - // Create session - const sessionId = generateId(40); - const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); - - const authSessionTable = schema.authSession; - await (db as any).insert(authSessionTable).values({ - id: sessionId, - user_id: newUserId, - expires_at: expiresAt.getTime() - }); - - const sessionCookie = getLucia().createSessionCookie(sessionId); - reply.setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - - const frontendUrl = await GlobalSettingsInitService.getPageUrl(); - return reply.redirect(frontendUrl); - - } catch (error) { - fastify.log.error(error, 'Error during Google OAuth callback:'); - return reply.status(500).send({ error: 'An unexpected error occurred during Google login.' }); - } - }); -} -``` - -### 5. Create Status Endpoint - -Create a status endpoint for the provider: - -```typescript -// services/backend/src/routes/auth/googleStatus.ts -import type { FastifyInstance } from 'fastify'; -import { z } from 'zod'; -import { createSchema } from 'zod-openapi'; -import { GlobalSettingsInitService } from '../../global-settings'; - -const GoogleStatusResponseSchema = z.object({ - enabled: z.boolean(), - configured: z.boolean(), - callbackUrl: z.string().optional(), -}); - -export default async function googleStatusRoutes(fastify: FastifyInstance) { - fastify.get('/status', { - schema: { - tags: ['Authentication'], - summary: 'Get Google OAuth status', - description: 'Returns the current status and configuration of Google OAuth', - response: { - 200: createSchema(GoogleStatusResponseSchema) - } - } - }, async (_request, reply) => { - const googleConfig = await GlobalSettingsInitService.getGoogleOAuthConfiguration(); - - return reply.send({ - enabled: googleConfig !== null, - configured: googleConfig !== null && !!googleConfig.clientId && !!googleConfig.clientSecret, - callbackUrl: googleConfig?.callbackUrl, - }); - }); -} -``` - -### 6. Register Routes - -Add the new routes to your route registration: - -```typescript -// services/backend/src/routes/auth/index.ts -import googleAuthRoutes from './google'; -import googleStatusRoutes from './googleStatus'; - -export default async function authRoutes(fastify: FastifyInstance) { - // Register Google OAuth routes - await fastify.register(googleAuthRoutes, { prefix: '/google' }); - await fastify.register(googleStatusRoutes, { prefix: '/google' }); -} -``` - -### 7. Update Database Schema - -Add the provider-specific field to your user schema: - -```typescript -// services/backend/src/db/schema.sqlite.ts -export const authUser = sqliteTable('authUser', { - // ... existing fields - google_id: text('google_id').unique(), - // ... other fields -}); -``` - -### 8. Generate Database Migration - -Run the migration generation command: - -```bash -cd services/backend -npm run db:generate -``` - -## Provider-Specific Considerations - -### Google OAuth - -- **Scopes**: Use `openid email profile` for basic user information -- **User Info Endpoint**: `https://www.googleapis.com/oauth2/v2/userinfo` -- **Email**: Always available in the user info response - -### Microsoft OAuth - -- **Scopes**: Use `openid email profile` or `User.Read` -- **User Info Endpoint**: `https://graph.microsoft.com/v1.0/me` -- **Email**: Available as `mail` or `userPrincipalName` - -### Facebook OAuth - -- **Scopes**: Use `email public_profile` -- **User Info Endpoint**: `https://graph.facebook.com/me?fields=id,name,email` -- **Email**: Requires explicit permission and may not always be available - -## Best Practices - -### Security - -1. **State Parameter**: Always validate the state parameter to prevent CSRF attacks -2. **Secure Cookies**: Use secure, httpOnly cookies for state storage -3. **HTTPS**: Always use HTTPS in production -4. **Scope Minimization**: Request only the scopes you actually need - -### Error Handling - -1. **Graceful Degradation**: Handle cases where email is not available -2. **User Feedback**: Provide clear error messages for common issues -3. **Logging**: Log errors for debugging but don't expose sensitive information - -### Database Design - -1. **Provider IDs**: Store provider-specific user IDs for account linking -2. **Email Verification**: Mark OAuth emails as verified by default -3. **Account Linking**: Allow users to link multiple OAuth providers - -### Testing - -1. **Mock Providers**: Use mock OAuth providers for testing -2. **State Validation**: Test state parameter validation -3. **Error Scenarios**: Test various error conditions - -## Common Issues - -### Email Not Available - -Some providers may not provide email addresses. Handle this gracefully: - -```typescript -if (!userEmail) { - return reply.status(400).send({ - error: 'Email address is required but not provided by the OAuth provider.' - }); -} -``` - -### Account Conflicts - -Handle cases where a user tries to link an OAuth account that's already linked: - -```typescript -if (existingUser.length > 0 && existingUser[0].id !== currentUserId) { - return reply.status(409).send({ - error: 'This OAuth account is already linked to another user.' - }); -} -``` - -### Session Creation Issues - -If you encounter session creation issues, use the manual session creation approach as shown in the GitHub implementation. - -## Resources - -- [Arctic Documentation](https://arctic.js.org/) -- [Lucia Documentation](https://lucia-auth.com/) -- [OAuth 2.0 RFC](https://tools.ietf.org/html/rfc6749) -- [OpenID Connect](https://openid.net/connect/) diff --git a/docs/development/backend/oauth2-server.mdx b/docs/development/backend/oauth2-server.mdx new file mode 100644 index 0000000..9d962a2 --- /dev/null +++ b/docs/development/backend/oauth2-server.mdx @@ -0,0 +1,343 @@ +--- +title: OAuth2 Server Implementation +description: Developer guide for the OAuth2 authorization server that enables programmatic API access via Bearer tokens +--- + +# OAuth2 Server Implementation + +This document describes the OAuth2 authorization server implementation in the DeployStack backend, which enables CLI tools and applications to access APIs using Bearer tokens. For general authentication, see [Backend Authentication System](/development/backend/auth). For OAuth provider integration (social login), see [GitHub OAuth Implementation](/development/backend/github-oauth). + +## Overview + +The OAuth2 server provides RFC 6749 compliant authorization for programmatic API access. This enables the DeployStack Gateway CLI and other tools to authenticate users and access APIs on their behalf using Bearer tokens instead of cookies. + +## Architecture + +The OAuth2 server implementation includes: + +- **Authorization Server** - Handles OAuth2 authorization flow with PKCE +- **Token Management** - Issues and validates access/refresh tokens +- **Consent System** - User authorization interface +- **Dual Authentication** - Supports both cookies and Bearer tokens +- **Scope-based Access** - Fine-grained permission control + +## OAuth2 Flow + +### Authorization Code Flow with PKCE + +The implementation follows the OAuth2 Authorization Code flow enhanced with PKCE (Proof Key for Code Exchange) for additional security: + +1. **Client generates PKCE challenge** - Creates code verifier and SHA256 challenge +2. **Authorization request** - Client redirects to `/api/oauth2/auth` +3. **User consent** - User approves requested scopes +4. **Authorization code** - Server returns code to callback +5. **Token exchange** - Client exchanges code for tokens +6. **API access** - Client uses Bearer token for requests + +### PKCE Implementation + +PKCE provides security for public clients (like CLI tools): + +#### Code Verifier +- 128 random bytes encoded as base64url +- Generated by client, kept secret +- Used during token exchange + +#### Code Challenge +- SHA256 hash of verifier +- Sent with authorization request +- Stored with authorization code + +#### Validation +- Server verifies challenge matches verifier +- Prevents code interception attacks +- Required for all authorization requests + +## Service Architecture + +### AuthorizationService + +Manages the authorization flow: + +#### Client Validation +- Validates client_id against whitelist +- Currently supports `deploystack-gateway-cli` +- Extensible for additional clients + +#### Redirect URI Validation +- Checks URI against allowed list +- Supports localhost callbacks for CLI +- Prevents redirect attacks + +#### Scope Validation +- Validates requested scopes +- Ensures scopes are recognized +- Limits access appropriately + +#### Authorization Storage +- Stores authorization requests +- Links PKCE challenges +- Manages request lifecycle + +#### Code Generation +- Creates authorization codes +- Associates with user session +- Implements expiration + +#### Code Verification +- Validates authorization codes +- Verifies PKCE challenge +- Ensures single use + +### TokenService + +Handles token lifecycle: + +#### Token Generation +- Creates cryptographically secure tokens +- Generates appropriate expiration +- Stores hashed versions + +#### Access Token Management +- Issues 1-hour access tokens +- Includes user and scope data +- Enables API authentication + +#### Refresh Token Handling +- Issues 30-day refresh tokens +- Allows token renewal +- Maintains session continuity + +#### Token Verification +- Validates token format +- Checks expiration +- Verifies against database + +#### Token Refresh +- Exchanges refresh for access token +- Validates client identity +- Maintains scope consistency + +#### Token Revocation +- Invalidates tokens on demand +- Cleans up related tokens +- Ensures immediate effect + +### OAuthCleanupService + +Automatic maintenance: + +#### Scheduled Cleanup +- Runs hourly via cron +- Removes expired tokens +- Prevents database bloat + +#### Cleanup Scope +- Authorization codes > 10 minutes +- Expired access tokens +- Expired refresh tokens + +## OAuth2 Endpoints Overview + +The OAuth2 server implements standard OAuth2 endpoints following RFC 6749: + +### Authorization Flow Endpoints + +- **Authorization Endpoint** (`/api/oauth2/auth`) - Initiates the OAuth2 flow with PKCE parameters +- **Consent Endpoints** (`/api/oauth2/consent`) - Displays and processes user authorization consent +- **Token Endpoint** (`/api/oauth2/token`) - Exchanges authorization codes for access tokens and handles token refresh +- **User Info Endpoint** (`/api/oauth2/userinfo`) - Returns authenticated user information + +For complete API specifications including request parameters, response schemas, and examples, see the [Backend API Documentation](/development/backend/api). The API documentation provides OpenAPI specifications for all OAuth2 endpoints. + +## OAuth2 Scopes + +### 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 + +### Scope Enforcement + +Scopes are enforced at the endpoint level: + +#### Middleware Integration +- `requireOAuthScope()` - Single scope requirement +- `requireAnyOAuthScope()` - Multiple scope options +- Skip enforcement for cookie auth + +#### Scope Checking +- Validates token contains required scope +- Returns 403 for insufficient scope +- Provides clear error messages + +## Dual Authentication + +### Supporting Both Methods + +Endpoints can accept cookies or Bearer tokens: + +#### Middleware: `requireAuthenticationAny()` + +1. First checks cookie session (from authHook) +2. Falls back to Bearer token validation +3. Populates unified `request.user` +4. Maintains authentication type context + +#### Implementation Pattern + +Routes support both authentication methods: +- Define both security schemes in OpenAPI +- Use dual authentication middleware +- Apply scope requirements conditionally +- Handle both response formats + +### Authentication Context + +The system maintains context about authentication: + +- **Cookie Auth**: `request.user` and `request.session` +- **OAuth2 Auth**: `request.user` and `request.tokenPayload` +- **Type Detection**: Check for `tokenPayload` presence +- **Unified Interface**: Same user object structure + +## Client Configuration + +### DeployStack Gateway CLI + +Pre-registered OAuth2 client: + +- **Client ID**: `deploystack-gateway-cli` +- **Client Type**: Public (no secret) +- **Redirect URIs**: + - `http://localhost:8976/oauth/callback` + - `http://127.0.0.1:8976/oauth/callback` +- **Required**: PKCE with SHA256 +- **Token Lifetime**: 1-hour access, 30-day refresh + +### Adding New Clients + +To support additional OAuth2 clients: + +1. Add client_id to validation whitelist +2. Configure allowed redirect URIs +3. Define client-specific settings +4. Update documentation + +## Security Implementation + +### PKCE Security + +Protection against authorization code interception: +- Required for all authorization requests +- SHA256 challenge method only +- Cryptographically secure verifier generation +- Single-use authorization codes + +### Token Security + +Multiple layers of token protection: +- Argon2 hashing for stored tokens +- Constant-time comparison +- Secure random generation +- Automatic expiration +- Regular cleanup + +### Authorization Security + +Secure authorization flow: +- CSRF protection via state parameter +- Session requirement for authorization +- Validated redirect URIs +- Clear consent interface + +### Bearer Token Security + +API access security: +- Standard Authorization header +- Token validation on each request +- Scope-based access control +- Automatic token refresh + +## Integration Examples + +### CLI Authentication Flow + +Example OAuth2 flow for CLI tools: + +1. **Generate PKCE challenge** +2. **Open browser for authorization** +3. **Start callback server** +4. **User approves in browser** +5. **Receive authorization code** +6. **Exchange for tokens** +7. **Store tokens securely** +8. **Use Bearer token for API** + +### API Request Example + +Using Bearer token for API access: + +``` +GET /api/teams/me/default +Authorization: Bearer +``` + +### Token Refresh Example + +Refreshing expired access token: + +``` +POST /api/oauth2/token +Content-Type: application/json + +{ + "grant_type": "refresh_token", + "refresh_token": "", + "client_id": "deploystack-gateway-cli" +} +``` +## Monitoring + +### Metrics to Track (TODO) + +- Authorization requests +- Token issuance rate +- Refresh token usage +- Failed authentication attempts +- Cleanup effectiveness + +### Logging (TODO) + +Comprehensive logging for debugging: +- Authorization flow steps +- Token operations +- Scope validations +- Error conditions +- Security events + +## Gateway Integration + +The OAuth2 server integrates with the DeployStack Gateway: + +### Gateway OAuth Client + +See [Gateway OAuth Implementation](/development/gateway/oauth) for: +- Client-side PKCE generation +- Browser integration +- Callback server +- Token storage +- Automatic refresh + +## Related Documentation + +- [Backend Authentication System](/development/backend/auth) - Core authentication +- [Gateway OAuth Implementation](/development/gateway/oauth) - Client-side OAuth +- [Security Policy](/development/backend/security) - Security details +- [API Documentation](/development/backend/api) - API reference +- [OAuth Provider Implementation](/development/backend/oauth-providers) - Third-party OAuth login setup diff --git a/docs/index.mdx b/docs/index.mdx index c971938..d46b0f1 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -33,9 +33,8 @@ DeployStack is the **Enterprise Control Plane for the Model Context Protocol (MC } - href="/self-hosted/" + href="/self-hosted" title="Self-Hosted DeployStack" - external > Host DeployStack on your own infrastructure for complete control and security From b1be14949123352fe59513e3d5b1ca211716d305 Mon Sep 17 00:00:00 2001 From: Lasim Date: Sat, 9 Aug 2025 17:15:37 +0200 Subject: [PATCH 4/4] fix: update links to OAuth implementation guides for consistency --- docs/development/backend/api-security.mdx | 2 +- docs/development/backend/oauth2-server.mdx | 2 +- docs/development/backend/security.mdx | 2 +- docs/development/gateway/oauth.mdx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/development/backend/api-security.mdx b/docs/development/backend/api-security.mdx index 22bece9..d7d1baa 100644 --- a/docs/development/backend/api-security.mdx +++ b/docs/development/backend/api-security.mdx @@ -179,7 +179,7 @@ fastify.get('/dual-auth-endpoint', { }); ``` -For detailed OAuth2 implementation, see the [Backend OAuth Implementation Guide](/development/backend/oauth) and [Backend Security Policy](/development/backend/security#oauth2-server-security). +For detailed OAuth2 implementation, see the [Backend OAuth Implementation Guide](/development/backend/oauth-providers) and [Backend Security Policy](/development/backend/security#oauth2-server-security). ### Team-Aware Permission System diff --git a/docs/development/backend/oauth2-server.mdx b/docs/development/backend/oauth2-server.mdx index 9d962a2..baeb7b8 100644 --- a/docs/development/backend/oauth2-server.mdx +++ b/docs/development/backend/oauth2-server.mdx @@ -5,7 +5,7 @@ description: Developer guide for the OAuth2 authorization server that enables pr # OAuth2 Server Implementation -This document describes the OAuth2 authorization server implementation in the DeployStack backend, which enables CLI tools and applications to access APIs using Bearer tokens. For general authentication, see [Backend Authentication System](/development/backend/auth). For OAuth provider integration (social login), see [GitHub OAuth Implementation](/development/backend/github-oauth). +This document describes the OAuth2 authorization server implementation in the DeployStack backend, which enables CLI tools and applications to access APIs using Bearer tokens. For general authentication, see [Backend Authentication System](/development/backend/auth). For OAuth provider integration (social login), see [Providers OAuth Implementation](/development/backend/oauth-providers). ## Overview diff --git a/docs/development/backend/security.mdx b/docs/development/backend/security.mdx index 4bc9d26..69bff57 100644 --- a/docs/development/backend/security.mdx +++ b/docs/development/backend/security.mdx @@ -105,7 +105,7 @@ The OAuth2 server implementation follows RFC 6749 and RFC 7636 security standard - **Scope Enforcement:** Middleware validates required scopes per endpoint - **Dual Authentication:** Seamless support for both cookies and Bearer tokens -For implementation details, see the [Backend OAuth Implementation Guide](/development/backend/oauth). +For implementation details, see the [Backend OAuth Implementation Guide](/development/backend/oauth-providers). ## Dependencies diff --git a/docs/development/gateway/oauth.mdx b/docs/development/gateway/oauth.mdx index 86a98b0..7653e0d 100644 --- a/docs/development/gateway/oauth.mdx +++ b/docs/development/gateway/oauth.mdx @@ -190,7 +190,7 @@ The OAuth client adapts to different environments: ## Integration with Backend -The gateway OAuth client integrates with the [backend OAuth2 server](/development/backend/oauth#oauth2-server-implementation): +The gateway OAuth client integrates with the [backend OAuth2 server](/development/backend/oauth2-server): - **Client Registration**: Pre-registered with known client ID - **PKCE Support**: Uses SHA256 method as required by backend