diff --git a/ADMIN_README.md b/ADMIN_README.md index adca5c5..6805d72 100644 --- a/ADMIN_README.md +++ b/ADMIN_README.md @@ -1,6 +1,43 @@ # Postgres Web Admin Panel -A modern web-based admin interface for PostgreSQL with Material UI. +A **modern, beautiful replacement for legacy database admin tools** like phpMyAdmin, Adminer, and pgAdmin. + +Built with Next.js 16, Material UI, and TypeScript for a superior user experience. + +## Why This Instead of Legacy Tools? + +### 🎨 Modern vs. Crusty +- **Beautiful Material UI** instead of outdated 2000s-era interfaces +- **Dark mode friendly** and responsive design +- **Fast, smooth interactions** with React and Next.js +- **Clean, intuitive navigation** vs cluttered legacy UIs + +### 🚀 All-in-One Solution +- **Includes PostgreSQL** - no separate database setup needed +- **Docker-ready** - deploy anywhere in seconds +- **Zero configuration** - works out of the box +- **Built-in authentication** - no complicated auth setup + +### 🔒 Security First +- **Modern authentication** with bcrypt + JWT +- **SQL injection protection** with multiple layers +- **Session management** with HTTP-only cookies +- **Auto-generated passwords** - no default "admin/admin" + +### đŸ’ŧ Production Ready +- **Caprover compatible** - deploy with one click +- **GitHub Container Registry** - automated CI/CD +- **Cloudflare Tunnel support** - easy HTTPS +- **Persistent storage** - data survives restarts + +## Replaces These Legacy Tools + +| Old Tool | Issues | This Solution | +|----------|--------|---------------| +| **phpMyAdmin** | PHP-based, outdated UI, MySQL-focused | Modern Next.js, beautiful UI, PostgreSQL-focused | +| **Adminer** | Single PHP file, basic features | Full-featured app with authentication | +| **pgAdmin** | Heavy desktop app, complex setup | Lightweight web app, simple deployment | +| **SQL Workbench** | Desktop only, OS-specific | Web-based, works everywhere | ## Features @@ -30,15 +67,56 @@ Create an admin user for logging into the admin panel: npm run db:seed-admin ``` -This creates a default admin user: -- **Username**: admin -- **Password**: admin123 +**Auto-generated password**: If you don't provide a password, a secure 32-character password will be automatically generated: + +```bash +✅ Admin user created successfully! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📧 Username: admin +🔑 Password: aB3$xK9@mP2&vL8#qR5!wN7^zT4%yU6* +âš ī¸ This password was auto-generated. Save it securely! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌐 Login at: http://localhost:3000/admin/login +``` + +**Custom credentials**: You can also provide custom credentials: -You can customize these credentials by setting environment variables: ```bash ADMIN_USERNAME=myuser ADMIN_PASSWORD=mypassword npm run db:seed-admin ``` +### Generate Secure Passwords + +Use the built-in password generator to create secure passwords: + +```bash +# Generate a 32-character password (default) +npm run generate:password + +# Generate a 64-character password +npm run generate:password 64 + +# Generate without special characters +npm run generate:password 32 false +``` + +Example output: +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🔐 Secure Password Generated +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Password: xK9@mP2&vL8#qR5!wN7^zT4%yU6*aB3$ +Length: 32 characters +Special characters: Yes +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +âš ī¸ Save this password securely! + +💡 Usage examples: + ADMIN_PASSWORD="xK9@mP2&vL8#qR5!wN7^zT4%yU6*aB3$" npm run db:seed-admin + export JWT_SECRET="xK9@mP2&vL8#qR5!wN7^zT4%yU6*aB3$" +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + ### 3. Access the Admin Panel Start the development server: @@ -48,6 +126,166 @@ npm run dev Navigate to: **http://localhost:3000/admin/login** +## Caprover Deployment + +The application is ready to deploy on Caprover with minimal configuration. + +### Deploy to Caprover + +1. **Create a new app** in your Caprover dashboard + - App Name: `postgres-admin` (or your choice) + - Enable HTTPS (Caprover handles SSL automatically) + +2. **Deploy via Dockerfile**: + - Caprover will automatically use the Dockerfile in the repository + - No additional configuration needed! + +3. **Set Environment Variables** in Caprover: + ``` + JWT_SECRET= + CREATE_ADMIN_USER=true + ADMIN_USERNAME=admin + ADMIN_PASSWORD=your-secure-password + ``` + +4. **Access your admin panel**: + - https://postgres-admin.your-caprover-domain.com/admin/login + +### Captain Definition (Optional) + +If you want to customize the build, create `captain-definition` in the root: + +```json +{ + "schemaVersion": 2, + "dockerfilePath": "./Dockerfile" +} +``` + +### Caprover One-Click App (Optional) + +For easier deployment, you can also deploy as a one-click app. The all-in-one Docker image includes PostgreSQL and Next.js, so no external database needed! + +### Notes + +- **HTTPS**: Caprover automatically provides HTTPS via Let's Encrypt +- **Built-in Database**: The Docker image includes PostgreSQL, no need for separate database setup +- **Persistent Storage**: Caprover automatically handles volume persistence +- **Auto-restart**: Caprover restarts the container automatically on failure + +## Cloudflare Tunnel Deployment (Alternative) + +
+Click to expand Cloudflare Tunnel instructions + +The application works seamlessly with Cloudflare Tunnel for secure HTTPS access without exposing ports. + +### Prerequisites + +1. Install `cloudflared`: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/ +2. Cloudflare account with a domain + +### Quick Setup with Cloudflare Tunnel + +1. **Start the application**: + ```bash + docker-compose up -d + ``` + +2. **Create a Cloudflare Tunnel**: + ```bash + cloudflared tunnel login + cloudflared tunnel create postgres-admin + ``` + +3. **Create tunnel configuration** (`~/.cloudflared/config.yml`): + ```yaml + tunnel: + credentials-file: /home/user/.cloudflared/.json + + ingress: + - hostname: postgres-admin.yourdomain.com + service: http://localhost:3000 + - service: http_status:404 + ``` + +4. **Route DNS**: + ```bash + cloudflared tunnel route dns postgres-admin postgres-admin.yourdomain.com + ``` + +5. **Run the tunnel**: + ```bash + cloudflared tunnel run postgres-admin + ``` + +6. **Access your admin panel** at: + - https://postgres-admin.yourdomain.com/admin/login + +### Docker Compose with Cloudflare Tunnel + +Create a complete setup with tunnel included: + +```yaml +version: '3.8' + +services: + postgres-admin: + build: . + ports: + - '3000:3000' + environment: + - DATABASE_URL=postgresql://docker:docker@localhost:5432/postgres + - JWT_SECRET=your-secret-key-change-in-production + - CREATE_ADMIN_USER=true + - ADMIN_USERNAME=admin + - ADMIN_PASSWORD=admin123 + volumes: + - postgres_data:/var/lib/postgresql/15/main + + cloudflared: + image: cloudflare/cloudflared:latest + command: tunnel --no-autoupdate run + environment: + - TUNNEL_TOKEN= + depends_on: + - postgres-admin + restart: unless-stopped + +volumes: + postgres_data: +``` + +Get your tunnel token from: https://one.dash.cloudflare.com/ + +### Security Considerations with Cloudflare + +✅ **Automatic HTTPS** - Cloudflare provides SSL/TLS automatically +✅ **DDoS Protection** - Built-in Cloudflare protection +✅ **Access Control** - Use Cloudflare Access for additional authentication +✅ **Rate Limiting** - Configure Cloudflare rate limits +✅ **WAF** - Web Application Firewall protection + +### Recommended Cloudflare Settings + +1. **SSL/TLS Mode**: Full (strict) recommended +2. **Always Use HTTPS**: Enabled +3. **Automatic HTTPS Rewrites**: Enabled +4. **HTTP Strict Transport Security (HSTS)**: Enabled +5. **Rate Limiting**: Configure for /api/* endpoints + +### Cloudflare Access (Optional Extra Security) + +Add an extra authentication layer: + +```bash +# In Cloudflare Dashboard > Access > Applications +# Create a new application for postgres-admin.yourdomain.com +# Add authentication methods (Email OTP, Google, etc.) +``` + +
+ ## Docker Deployment The project includes an **all-in-one Docker image** that contains both PostgreSQL and the Next.js application, making deployment simple and straightforward. diff --git a/package.json b/package.json index 800102e..af70cb6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "db:migrate": "dotenv -c -- drizzle-kit migrate", "db:studio": "drizzle-kit studio", "db:seed-admin": "tsx scripts/seed-admin.ts", + "generate:password": "tsx scripts/generate-password.ts", "storybook": "storybook dev -p 6006", "storybook:test": "vitest run --config .storybook/vitest.config.mts", "build-storybook": "storybook build" diff --git a/scripts/generate-password.ts b/scripts/generate-password.ts new file mode 100644 index 0000000..a56fd35 --- /dev/null +++ b/scripts/generate-password.ts @@ -0,0 +1,63 @@ +#!/usr/bin/env node + +import * as crypto from 'node:crypto'; + +/** + * Generate a secure random password + * @param length - Length of the password (default: 32) + * @param includeSpecial - Include special characters (default: true) + */ +function generateSecurePassword(length = 32, includeSpecial = true): string { + let charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + if (includeSpecial) { + charset += '!@#$%^&*()-_=+[]{}|;:,.<>?'; + } + + const randomBytes = crypto.randomBytes(length); + let password = ''; + + for (let i = 0; i < length; i++) { + const byte = randomBytes[i]; + if (byte !== undefined) { + password += charset[byte % charset.length]; + } + } + + return password; +} + +// CLI interface +if (require.main === module) { + const args = process.argv.slice(2); + const length = args[0] ? Number.parseInt(args[0], 10) : 32; + const includeSpecial = args[1] !== 'false'; + + if (Number.isNaN(length) || length < 8) { + console.error('Error: Password length must be at least 8 characters'); + process.exit(1); + } + + if (length > 128) { + console.error('Error: Password length cannot exceed 128 characters'); + process.exit(1); + } + + const password = generateSecurePassword(length, includeSpecial); + + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('🔐 Secure Password Generated'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log(`Password: ${password}`); + console.log(`Length: ${password.length} characters`); + console.log(`Special characters: ${includeSpecial ? 'Yes' : 'No'}`); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('âš ī¸ Save this password securely!'); + console.log(''); + console.log('💡 Usage examples:'); + console.log(` ADMIN_PASSWORD="${password}" npm run db:seed-admin`); + console.log(` export JWT_SECRET="${password}"`); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); +} + +export { generateSecurePassword }; diff --git a/scripts/seed-admin.ts b/scripts/seed-admin.ts index d635298..cd8922a 100644 --- a/scripts/seed-admin.ts +++ b/scripts/seed-admin.ts @@ -2,6 +2,7 @@ import * as bcrypt from 'bcryptjs'; import { drizzle } from 'drizzle-orm/node-postgres'; import { Pool } from 'pg'; import { adminUserSchema } from '../src/models/Schema'; +import { generateSecurePassword } from './generate-password'; async function seedAdminUser() { const pool = new Pool({ @@ -11,7 +12,15 @@ async function seedAdminUser() { const db = drizzle(pool); const username = process.env.ADMIN_USERNAME || 'admin'; - const password = process.env.ADMIN_PASSWORD || 'admin123'; + + // Generate secure password if not provided + let password = process.env.ADMIN_PASSWORD; + let passwordGenerated = false; + + if (!password) { + password = generateSecurePassword(32); + passwordGenerated = true; + } const passwordHash = await bcrypt.hash(password, 10); @@ -21,14 +30,20 @@ async function seedAdminUser() { passwordHash, }); - console.log(`Admin user created successfully!`); - console.log(`Username: ${username}`); - console.log(`Password: ${password}`); + console.log('✅ Admin user created successfully!'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log(`📧 Username: ${username}`); + console.log(`🔑 Password: ${password}`); + if (passwordGenerated) { + console.log('âš ī¸ This password was auto-generated. Save it securely!'); + } + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('🌐 Login at: http://localhost:3000/admin/login'); } catch (error: any) { if (error.code === '23505') { - console.log('Admin user already exists'); + console.log('â„šī¸ Admin user already exists'); } else { - console.error('Error creating admin user:', error); + console.error('❌ Error creating admin user:', error); } } finally { await pool.end(); diff --git a/src/app/api/admin/query/route.ts b/src/app/api/admin/query/route.ts index 13c36c6..a47b603 100644 --- a/src/app/api/admin/query/route.ts +++ b/src/app/api/admin/query/route.ts @@ -15,7 +15,8 @@ function validateSelectQuery(query: string): boolean { } // Check for dangerous keywords (case insensitive) - const dangerous = /;\s*(?:drop|delete|update|insert|alter|create|truncate|exec|execute)\s/i; + // Includes common SQL modification commands and advanced features + const dangerous = /;\s*(?:drop|delete|update|insert|alter|create|truncate|exec|execute|merge|call|with)\s/i; if (dangerous.test(trimmed)) { return false; } diff --git a/src/app/api/admin/table-data/route.ts b/src/app/api/admin/table-data/route.ts index e14f727..8b83a83 100644 --- a/src/app/api/admin/table-data/route.ts +++ b/src/app/api/admin/table-data/route.ts @@ -38,9 +38,10 @@ export async function POST(request: Request) { ); } - // Table name is validated, sanitize and use - const safeTableName = String(tableName).replace(/\W/g, ''); - const result = await db.execute(sql.raw(`SELECT * FROM "${safeTableName}" LIMIT 100`)); + // Table name is validated against schema - safe to use the validated name + // The validation query above ensures the table exists in the public schema + const validatedTableName = (tablesResult.rows[0] as any).table_name; + const result = await db.execute(sql.raw(`SELECT * FROM "${validatedTableName}" LIMIT 100`)); return NextResponse.json({ rows: result.rows,