diff --git a/.env.backup b/.env.backup new file mode 100644 index 0000000..4252369 --- /dev/null +++ b/.env.backup @@ -0,0 +1,66 @@ +# Database Configuration +POSTGRES_HOST=localhost +POSTGRES_PORT=5433 +POSTGRES_DB=dev_pipeline +POSTGRES_USER=pipeline_admin +POSTGRES_PASSWORD=secure_pipeline_2024 + +# Redis Configuration +REDIS_PASSWORD=redis_secure_2024 + +# MongoDB Configuration +MONGO_INITDB_ROOT_USERNAME=pipeline_admin +MONGO_INITDB_ROOT_PASSWORD=mongo_secure_2024 + +# RabbitMQ Configuration +RABBITMQ_DEFAULT_USER=pipeline_admin +RABBITMQ_DEFAULT_PASS=rabbit_secure_2024 + +# n8n Configuration +N8N_BASIC_AUTH_USER=admin +N8N_BASIC_AUTH_PASSWORD=admin_n8n_2024 +N8N_ENCRYPTION_KEY=very_secure_encryption_key_2024 + +# Jenkins Configuration +JENKINS_ADMIN_ID=admin +JENKINS_ADMIN_PASSWORD=jenkins_secure_2024 + +# Gitea Configuration +GITEA_ADMIN_USER=admin +GITEA_ADMIN_PASSWORD=gitea_secure_2024 + +# API Keys (add your actual keys later) +CLAUDE_API_KEY=your_claude_api_key_here +OPENAI_API_KEY=your_openai_api_key_here +CLOUDTOPIAA_API_KEY=your_cloudtopiaa_api_key_here +CLOUDTOPIAA_API_URL=https://api.cloudtopiaa.com + +# JWT Configuration +JWT_SECRET=ultra_secure_jwt_secret_2024 + +# Environment +ENVIRONMENT=development +NODE_ENV=development +PYTHONPATH=/app/src + +# Monitoring +GRAFANA_ADMIN_USER=admin +GRAFANA_ADMIN_PASSWORD=grafana_secure_2024 +RABBITMQ_PASSWORD=rabbit_secure_2024 +MONGODB_PASSWORD=pipeline_password +MONGODB_PASSWORD=pipeline_password +CLAUDE_API_KEY=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 +CLAUDE_API_KEY=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 + + +# SMTP Configuration (Option 1) +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +SMTP_USER=frontendtechbiz@gmail.com +SMTP_PASS=oidhhjeasgzbqptq +SMTP_FROM=frontendtechbiz@gmail.com + +# Gmail Configuration (Option 2 - Alternative to SMTP) +GMAIL_USER=frontendtechbiz@gmail.com +GMAIL_APP_PASSWORD=oidhhjeasgzbqptq diff --git a/docker-compose.yml b/docker-compose.yml index 6cce152..2716c74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -794,7 +794,17 @@ services: - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} - JWT_ACCESS_EXPIRY=15m - JWT_REFRESH_EXPIRY=7d - - FRONTEND_URL=http://localhost:61004 + - FRONTEND_URL=http://localhost:3001 + # Email Configuration + - SMTP_HOST=${SMTP_HOST:-smtp.gmail.com} + - SMTP_PORT=${SMTP_PORT:-587} + - SMTP_SECURE=${SMTP_SECURE:-false} + - SMTP_USER=${SMTP_USER:-frontendtechbiz@gmail.com} + - SMTP_PASS=${SMTP_PASS:-oidhhjeasgzbqptq} + - SMTP_FROM=${SMTP_FROM:-frontendtechbiz@gmail.com} + - GMAIL_USER=${GMAIL_USER:-frontendtechbiz@gmail.com} + - GMAIL_APP_PASSWORD=${GMAIL_APP_PASSWORD:-oidhhjeasgzbqptq} + - AUTH_PUBLIC_URL=http://localhost:8011 networks: - pipeline_network depends_on: diff --git a/services/template-manager/src/routes/templates.js b/services/template-manager/src/routes/templates.js index 0455d27..017c62f 100644 --- a/services/template-manager/src/routes/templates.js +++ b/services/template-manager/src/routes/templates.js @@ -229,7 +229,8 @@ router.delete('/:id', async (req, res) => { } // Soft delete by updating the instance - await template.update({ is_active: false }); + // await template.update({ is_active: false }); + await Template.delete(id); res.json({ success: true, diff --git a/services/user-auth/README-EMAIL-SETUP.md b/services/user-auth/README-EMAIL-SETUP.md new file mode 100644 index 0000000..69a1e35 --- /dev/null +++ b/services/user-auth/README-EMAIL-SETUP.md @@ -0,0 +1,181 @@ +# Email Setup for User Auth Service + +## Overview + +The User Auth Service sends verification emails when users register. This document explains how to configure email functionality. + +## Problem + +If emails are not being sent, it's likely due to missing email configuration. The service will show errors like: +- "Email configuration is missing" +- "Failed to create email transporter" +- "Failed to send verification email" + +## Quick Fix + +### Option 1: Use the Setup Script (Recommended) + +```bash +cd automated-dev-pipeline/services/user-auth +./setup-email.sh +``` + +This interactive script will guide you through the setup process. + +### Option 2: Manual Configuration + +1. **Create .env file**: + ```bash + cd automated-dev-pipeline/services/user-auth + cp env.example .env + ``` + +2. **Edit .env file** with your email credentials + +## Email Configuration Options + +### Gmail (Recommended for Development) + +1. **Enable 2-Step Verification** on your Google account +2. **Generate App Password**: + - Go to https://myaccount.google.com/security + - Click "App passwords" + - Generate password for "Mail" +3. **Update .env file**: + ```env + GMAIL_USER=your-email@gmail.com + GMAIL_APP_PASSWORD=your-16-char-app-password + SMTP_FROM=your-email@gmail.com + ``` + +### Custom SMTP Server + +```env +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +SMTP_USER=your-email@gmail.com +SMTP_PASS=your-password +SMTP_FROM=your-email@gmail.com +``` + +### Development Mode (Mock Emails) + +If you don't want to set up real emails in development: + +```env +NODE_ENV=development +# No email credentials needed +``` + +The service will use mock emails and log what would be sent. + +## Environment Variables + +| Variable | Description | Required | Default | +|----------|-------------|----------|---------| +| `GMAIL_USER` | Gmail email address | If using Gmail | - | +| `GMAIL_APP_PASSWORD` | Gmail app password | If using Gmail | - | +| `SMTP_HOST` | SMTP server hostname | If using SMTP | - | +| `SMTP_PORT` | SMTP server port | If using SMTP | 587 | +| `SMTP_USER` | SMTP username | If using SMTP | - | +| `SMTP_PASS` | SMTP password | If using SMTP | - | +| `SMTP_FROM` | From email address | Yes | GMAIL_USER | +| `NODE_ENV` | Environment mode | No | development | + +## Testing Email Configuration + +### 1. Check Service Logs + +```bash +# View email-related logs +docker-compose logs user-auth | grep -E "(Email|SMTP|Gmail|Mock)" + +# View all logs +docker-compose logs user-auth +``` + +### 2. Test Registration + +1. Visit `http://localhost:3001/signup` +2. Register a new user +3. Check logs for email status + +### 3. Expected Log Output + +**Success (Real Email)**: +``` +โœ… Email transporter created successfully +๐Ÿ“ง Using Gmail configuration +โœ… Verification email sent successfully to user@example.com +โœ‰๏ธ Email sent to user@example.com. MessageID: +``` + +**Development Mode (Mock Email)**: +``` +โš ๏ธ No email configuration found. Using mock transporter for development. +๐Ÿ“ง [MOCK] Email would be sent: { to: 'user@example.com', subject: '...', from: '...' } +``` + +## Troubleshooting + +### Common Issues + +1. **"Email configuration is missing"** + - Solution: Set up email credentials in .env file + +2. **"Authentication failed"** + - Solution: Check username/password, enable 2FA for Gmail + +3. **"Connection timeout"** + - Solution: Check firewall, use correct port (587 for Gmail) + +4. **"Invalid credentials"** + - Solution: Use App Password, not regular Gmail password + +### Debug Steps + +1. **Check environment variables**: + ```bash + docker-compose exec user-auth env | grep -E "(SMTP|GMAIL|EMAIL)" + ``` + +2. **Test email service directly**: + ```bash + docker-compose exec user-auth node -e " + require('dotenv').config(); + const { sendMail } = require('./src/utils/email'); + sendMail('test@example.com', 'Test', 'Test email', '

Test

') + .then(() => console.log('Email sent')) + .catch(err => console.error('Email failed:', err.message)); + " + ``` + +3. **Verify network connectivity**: + ```bash + docker-compose exec user-auth ping smtp.gmail.com + ``` + +## Security Notes + +- **Never commit .env files** to version control +- **Use App Passwords** for Gmail, not regular passwords +- **Restrict SMTP access** to trusted IPs in production +- **Rotate credentials** regularly + +## Production Considerations + +- Use dedicated email service (SendGrid, Mailgun, etc.) +- Set up proper SPF/DKIM records +- Monitor email delivery rates +- Implement email templates +- Add retry logic for failed emails + +## Support + +If you continue having issues: + +1. Check the service logs for detailed error messages +2. Verify your email credentials are correct +3. Test with a simple email client first +4. Check if your email provider blocks automated emails diff --git a/services/user-auth/env.example b/services/user-auth/env.example new file mode 100644 index 0000000..397a91d --- /dev/null +++ b/services/user-auth/env.example @@ -0,0 +1,38 @@ +# Email Configuration for User Auth Service +# Copy this file to .env and fill in your actual values + +# SMTP Configuration (Option 1) +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +SMTP_USER=your-email@gmail.com +SMTP_PASS=your-app-password +SMTP_FROM=your-email@gmail.com + +# Gmail Configuration (Option 2 - Alternative to SMTP) +GMAIL_USER=your-email@gmail.com +GMAIL_APP_PASSWORD=your-app-password + +# Service Configuration +PORT=8011 +NODE_ENV=development +FRONTEND_URL=http://localhost:3001 +AUTH_PUBLIC_URL=http://localhost:8011 + +# Database Configuration +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_DB=dev_pipeline +POSTGRES_USER=pipeline_admin +POSTGRES_PASSWORD=your_database_password + +# Redis Configuration +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=your_redis_password + +# JWT Configuration +JWT_ACCESS_SECRET=your_access_secret_key +JWT_REFRESH_SECRET=your_refresh_secret_key +JWT_ACCESS_EXPIRY=15m +JWT_REFRESH_EXPIRY=7d diff --git a/services/user-auth/package-lock.json b/services/user-auth/package-lock.json index 984acdb..1f58a47 100644 --- a/services/user-auth/package-lock.json +++ b/services/user-auth/package-lock.json @@ -19,6 +19,7 @@ "joi": "^17.7.0", "jsonwebtoken": "^9.0.0", "morgan": "^1.10.0", + "nodemailer": "^7.0.5", "pg": "^8.8.0", "redis": "^4.6.0", "uuid": "^9.0.0" @@ -72,22 +73,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -128,14 +129,14 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -186,15 +187,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -244,9 +245,9 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", - "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dev": true, "license": "MIT", "dependencies": { @@ -258,13 +259,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -528,18 +529,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -927,9 +928,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -948,16 +949,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1184,9 +1185,9 @@ } }, "node_modules/@types/node": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", - "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "dev": true, "license": "MIT", "dependencies": { @@ -1525,9 +1526,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", "dev": true, "funding": [ { @@ -1545,8 +1546,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -1639,9 +1640,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001731", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", - "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "dev": true, "funding": [ { @@ -2075,9 +2076,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", + "version": "1.5.206", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.206.tgz", + "integrity": "sha512-/eucXSTaI8L78l42xPurxdBzPTjAkMVCQO7unZCWk9LnZiwKcSvQUhF4c99NWQLwMQXxjlfoQy0+8m9U2yEDQQ==", "dev": true, "license": "ISC" }, @@ -2999,9 +3000,9 @@ "license": "MIT" }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4044,6 +4045,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", + "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", diff --git a/services/user-auth/package.json b/services/user-auth/package.json index 52184b7..a75c3c5 100644 --- a/services/user-auth/package.json +++ b/services/user-auth/package.json @@ -22,6 +22,7 @@ "joi": "^17.7.0", "jsonwebtoken": "^9.0.0", "morgan": "^1.10.0", + "nodemailer": "^7.0.5", "pg": "^8.8.0", "redis": "^4.6.0", "uuid": "^9.0.0" diff --git a/services/user-auth/setup-email.sh b/services/user-auth/setup-email.sh new file mode 100755 index 0000000..5cdd2dd --- /dev/null +++ b/services/user-auth/setup-email.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +echo "๐Ÿ”ง Setting up email configuration for User Auth Service" +echo "======================================================" + +# Check if .env file exists +if [ -f ".env" ]; then + echo "โœ… .env file already exists" + echo "๐Ÿ“ง Current email configuration:" + grep -E "^(SMTP_|GMAIL_|EMAIL_)" .env 2>/dev/null || echo " No email configuration found" +else + echo "๐Ÿ“ Creating .env file from template..." + cp env.example .env + echo "โœ… .env file created from env.example" +fi + +echo "" +echo "๐Ÿ“ง Email Configuration Options:" +echo "1. Gmail (Recommended for development)" +echo "2. Custom SMTP Server" +echo "3. Skip email setup (use mock emails)" + +read -p "Choose an option (1-3): " choice + +case $choice in + 1) + echo "" + echo "๐Ÿ“ง Gmail Configuration" + echo "=====================" + echo "You'll need to create an App Password for your Gmail account:" + echo "1. Go to https://myaccount.google.com/security" + echo "2. Enable 2-Step Verification if not already enabled" + echo "3. Go to 'App passwords' and generate a new app password" + echo "4. Use that password below (not your regular Gmail password)" + echo "" + + read -p "Enter your Gmail address: " gmail_user + read -s -p "Enter your Gmail App Password: " gmail_pass + echo "" + + # Update .env file + sed -i.bak "s/GMAIL_USER=.*/GMAIL_USER=$gmail_user/" .env + sed -i.bak "s/GMAIL_APP_PASSWORD=.*/GMAIL_APP_PASSWORD=$gmail_pass/" .env + sed -i.bak "s/SMTP_FROM=.*/SMTP_FROM=$gmail_user/" .env + + echo "โœ… Gmail configuration updated in .env file" + ;; + 2) + echo "" + echo "๐Ÿ“ง Custom SMTP Configuration" + echo "============================" + + read -p "Enter SMTP host (e.g., smtp.gmail.com): " smtp_host + read -p "Enter SMTP port (e.g., 587): " smtp_port + read -p "Enter SMTP username/email: " smtp_user + read -s -p "Enter SMTP password: " smtp_pass + echo "" + read -p "Enter from email address: " smtp_from + + # Update .env file + sed -i.bak "s/SMTP_HOST=.*/SMTP_HOST=$smtp_host/" .env + sed -i.bak "s/SMTP_PORT=.*/SMTP_PORT=$smtp_port/" .env + sed -i.bak "s/SMTP_USER=.*/SMTP_USER=$smtp_user/" .env + sed -i.bak "s/SMTP_PASS=.*/SMTP_PASS=$smtp_pass/" .env + sed -i.bak "s/SMTP_FROM=.*/SMTP_FROM=$smtp_from/" .env + + echo "โœ… SMTP configuration updated in .env file" + ;; + 3) + echo "" + echo "โš ๏ธ Email setup skipped. Mock emails will be used in development." + echo " Users will still be able to register and login, but verification emails won't be sent." + ;; + *) + echo "โŒ Invalid option. Email setup skipped." + ;; +esac + +echo "" +echo "๐Ÿ”ง Next steps:" +echo "1. Review and edit .env file if needed" +echo "2. Restart the user-auth service: docker-compose restart user-auth" +echo "3. Check logs for email configuration status" +echo "" +echo "๐Ÿ“ง To test email configuration:" +echo " docker-compose logs user-auth | grep -E '(Email|SMTP|Gmail)'" +echo "" +echo "โœ… Email setup complete!" diff --git a/services/user-auth/src/app.js b/services/user-auth/src/app.js index 827c13c..fa01f28 100644 --- a/services/user-auth/src/app.js +++ b/services/user-auth/src/app.js @@ -1,4 +1,5 @@ -require('dotenv').config(); +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '../../.env') }); const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); @@ -44,7 +45,7 @@ const corsOptions = { origin: function (origin, callback) { // Allow requests from your web-dashboard and other services const allowedOrigins = [ - 'http://localhost:3001', // Web dashboard + 'http://localhost:3001', // Web dashboard (changed from 3000 to 3001 to avoid conflict with other services) 'http://localhost:8008', // Dashboard service 'http://localhost:8000', // API Gateway 'http://localhost:3000', // Development React diff --git a/services/user-auth/src/config/database.js b/services/user-auth/src/config/database.js index e37d601..d153a55 100644 --- a/services/user-auth/src/config/database.js +++ b/services/user-auth/src/config/database.js @@ -4,7 +4,7 @@ class Database { constructor() { this.pool = new Pool({ host: process.env.POSTGRES_HOST || 'localhost', - port: process.env.POSTGRES_PORT || 5433, + port: process.env.POSTGRES_PORT || 5433, // changed from 5432 to 5433 to avoid conflict with other services database: process.env.POSTGRES_DB || 'dev_pipeline', user: process.env.POSTGRES_USER || 'pipeline_admin', password: process.env.POSTGRES_PASSWORD || 'secure_pipeline_2024', diff --git a/services/user-auth/src/migrations/001_user_auth_schema.sql b/services/user-auth/src/migrations/001_user_auth_schema.sql index 114b984..11fd740 100644 --- a/services/user-auth/src/migrations/001_user_auth_schema.sql +++ b/services/user-auth/src/migrations/001_user_auth_schema.sql @@ -6,6 +6,7 @@ DROP TABLE IF EXISTS user_feature_preferences CASCADE; DROP TABLE IF EXISTS user_sessions CASCADE; DROP TABLE IF EXISTS refresh_tokens CASCADE; DROP TABLE IF EXISTS users CASCADE; +DROP TABLE IF EXISTS user_projects CASCADE; -- Enable UUID extension if not already enabled CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/services/user-auth/src/migrations/002_email_verification_schema.sql b/services/user-auth/src/migrations/002_email_verification_schema.sql new file mode 100644 index 0000000..2b1e171 --- /dev/null +++ b/services/user-auth/src/migrations/002_email_verification_schema.sql @@ -0,0 +1,47 @@ +-- Email Verification Database Schema Addition +-- Adds email verification tokens table and cleanup function + +-- Enable UUID extension if not already enabled (safe to rerun) +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Email verification tokens table - For storing verification tokens +CREATE TABLE IF NOT EXISTS email_verification_tokens ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token_hash VARCHAR(255) NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + is_used BOOLEAN DEFAULT FALSE, + used_at TIMESTAMP +); + +-- Indexes for performance +CREATE INDEX IF NOT EXISTS idx_email_verification_tokens_user_id ON email_verification_tokens(user_id); +CREATE INDEX IF NOT EXISTS idx_email_verification_tokens_token_hash ON email_verification_tokens(token_hash); + +-- Cleanup function for expired or used verification tokens +CREATE OR REPLACE FUNCTION cleanup_expired_verification_tokens() +RETURNS INTEGER AS $$ +DECLARE + deleted_count INTEGER; +BEGIN + DELETE FROM email_verification_tokens + WHERE expires_at < NOW() OR is_used = true; + + GET DIAGNOSTICS deleted_count = ROW_COUNT; + + RETURN deleted_count; +END; +$$ LANGUAGE plpgsql; + +-- Success message +SELECT 'Email Verification schema added successfully!' as message; + +-- Display added table +SELECT + schemaname, + tablename, + tableowner +FROM pg_tables +WHERE schemaname = 'public' +AND tablename = 'email_verification_tokens'; \ No newline at end of file diff --git a/services/user-auth/src/migrations/migrate.js b/services/user-auth/src/migrations/migrate.js index daaa1cc..66dec67 100644 --- a/services/user-auth/src/migrations/migrate.js +++ b/services/user-auth/src/migrations/migrate.js @@ -1,32 +1,102 @@ +// require('dotenv').config(); +// const fs = require('fs'); +// const path = require('path'); +// const database = require('../config/database'); + +// async function runMigrations() { +// console.log('๐Ÿš€ Starting User Auth database migration...'); + +// try { +// // Read the SQL migration file +// const migrationPath = path.join(__dirname, '001_user_auth_schema.sql'); +// const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); + +// console.log('๐Ÿ“„ Running migration: 001_user_auth_schema.sql'); + +// // Execute the migration +// await database.query(migrationSQL); + +// console.log('โœ… User Auth migration completed successfully!'); +// console.log('๐Ÿ“Š Database schema created:'); +// console.log(' - users table (with admin and test users)'); +// console.log(' - refresh_tokens table'); +// console.log(' - user_sessions table'); +// console.log(' - user_feature_preferences table'); +// console.log(' - user_projects table'); +// console.log(' - indexes and triggers'); +// console.log(' - cleanup functions'); + +// // Verify tables were created +// const result = await database.query(` +// SELECT +// schemaname, +// tablename, +// tableowner +// FROM pg_tables +// WHERE schemaname = 'public' +// AND tablename IN ('users', 'refresh_tokens', 'user_sessions', 'user_feature_preferences', 'user_projects') +// ORDER BY tablename +// `); + +// console.log('๐Ÿ” Verified tables:'); +// result.rows.forEach(row => { +// console.log(` - ${row.tablename} (owner: ${row.tableowner})`); +// }); + +// // Test initial users +// const userCount = await database.query('SELECT COUNT(*) as count FROM users'); +// console.log(`๐Ÿ‘ฅ Initial users created: ${userCount.rows[0].count}`); + +// console.log('๐Ÿ” Default credentials:'); +// console.log(' Admin: admin@tech4biz.com / admin123'); +// console.log(' Test: test@tech4biz.com / admin123'); +// console.log(' โš ๏ธ Change passwords in production!'); + +// } catch (error) { +// console.error('โŒ Migration failed:', error.message); +// console.error('๐Ÿ“ Error details:', error); +// process.exit(1); +// } finally { +// await database.close(); +// } +// } + +// // Run migration if called directly +// if (require.main === module) { +// runMigrations(); +// } + +// module.exports = { runMigrations }; + require('dotenv').config(); const fs = require('fs'); const path = require('path'); const database = require('../config/database'); async function runMigrations() { - console.log('๐Ÿš€ Starting User Auth database migration...'); + console.log('๐Ÿš€ Starting database migrations...'); + + const migrations = [ + '001_user_auth_schema.sql', + '002_email_verification_schema.sql' + ]; try { - // Read the SQL migration file - const migrationPath = path.join(__dirname, '001_user_auth_schema.sql'); - const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); + for (const migrationFile of migrations) { + const migrationPath = path.join(__dirname, migrationFile); + if (!fs.existsSync(migrationPath)) { + console.warn(`โš ๏ธ Migration file ${migrationFile} not found, skipping...`); + continue; + } + + const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); + console.log(`๐Ÿ“„ Running migration: ${migrationFile}`); + + await database.query(migrationSQL); + console.log(`โœ… Migration ${migrationFile} completed!`); + } - console.log('๐Ÿ“„ Running migration: 001_user_auth_schema.sql'); - - // Execute the migration - await database.query(migrationSQL); - - console.log('โœ… User Auth migration completed successfully!'); - console.log('๐Ÿ“Š Database schema created:'); - console.log(' - users table (with admin and test users)'); - console.log(' - refresh_tokens table'); - console.log(' - user_sessions table'); - console.log(' - user_feature_preferences table'); - console.log(' - user_projects table'); - console.log(' - indexes and triggers'); - console.log(' - cleanup functions'); - - // Verify tables were created + // Verify all tables const result = await database.query(` SELECT schemaname, @@ -34,7 +104,7 @@ async function runMigrations() { tableowner FROM pg_tables WHERE schemaname = 'public' - AND tablename IN ('users', 'refresh_tokens', 'user_sessions', 'user_feature_preferences', 'user_projects') + AND tablename IN ('users', 'refresh_tokens', 'user_sessions', 'user_feature_preferences', 'user_projects', 'email_verification_tokens') ORDER BY tablename `); @@ -43,21 +113,12 @@ async function runMigrations() { console.log(` - ${row.tablename} (owner: ${row.tableowner})`); }); - // Test initial users - const userCount = await database.query('SELECT COUNT(*) as count FROM users'); - console.log(`๐Ÿ‘ฅ Initial users created: ${userCount.rows[0].count}`); - - console.log('๐Ÿ” Default credentials:'); - console.log(' Admin: admin@tech4biz.com / admin123'); - console.log(' Test: test@tech4biz.com / admin123'); - console.log(' โš ๏ธ Change passwords in production!'); - } catch (error) { console.error('โŒ Migration failed:', error.message); console.error('๐Ÿ“ Error details:', error); process.exit(1); } finally { - await database.close(); + await database.close(); // Close connection via Database wrapper } } diff --git a/services/user-auth/src/routes/auth.js b/services/user-auth/src/routes/auth.js index 8c4f744..9f97dad 100644 --- a/services/user-auth/src/routes/auth.js +++ b/services/user-auth/src/routes/auth.js @@ -43,7 +43,7 @@ router.post('/register', /*registerRateLimit,*/ validateRegistration, async (req success: true, data: { user: user.toJSON(), - message: 'User registered successfully' + message: 'User registered successfully. Please check your email to verify your account.' }, message: 'Registration completed successfully' }); @@ -57,6 +57,29 @@ router.post('/register', /*registerRateLimit,*/ validateRegistration, async (req } }); +// GET /api/auth/verify-email - Verify email via token and redirect to login +router.get('/verify-email', async (req, res) => { + try { + const { token } = req.query; + await authService.verifyEmailToken(token); + + const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3001'; + const redirectUrl = `${frontendUrl}/signin?verified=true`; + // JSON fallback if not a browser navigation + if (req.get('Accept') && req.get('Accept').includes('application/json')) { + return res.json({ success: true, message: 'Email verified successfully', redirect: redirectUrl }); + } + return res.redirect(302, redirectUrl); + } catch (error) { + const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3001'; + const redirectUrl = `${frontendUrl}/signin?error=${encodeURIComponent(error.message)}`; + if (req.get('Accept') && req.get('Accept').includes('application/json')) { + return res.status(400).json({ success: false, message: error.message }); + } + return res.redirect(302, redirectUrl); + } +}); + // POST /api/auth/login - User login router.post('/login', /*loginRateLimit , */validateLogin, async (req, res) => { try { diff --git a/services/user-auth/src/services/authService.js b/services/user-auth/src/services/authService.js index feff24e..570ad0d 100644 --- a/services/user-auth/src/services/authService.js +++ b/services/user-auth/src/services/authService.js @@ -1,9 +1,10 @@ -//const bcrypt = require('bcryptjs'); +const bcrypt = require('bcryptjs'); const crypto = require('crypto'); const { v4: uuidv4 } = require('uuid'); const database = require('../config/database'); const jwtConfig = require('../config/jwt'); const User = require('../models/user'); +const { sendMail } = require('../utils/email'); class AuthService { // Register new user @@ -45,6 +46,27 @@ class AuthService { }); console.log(`๐Ÿ‘ค New user registered: ${newUser.email}`); + + // Send verification email (non-blocking but awaited to surface errors in dev) + try { + await this.sendVerificationEmail(newUser); + console.log(`โœ… Verification email sent successfully to ${newUser.email}`); + } catch (err) { + console.error('โŒ Failed to send verification email:', { + error: err.message, + user: newUser.email, + stack: err.stack + }); + + // In development, don't fail the registration if email fails + if (process.env.NODE_ENV === 'development') { + console.warn('โš ๏ธ Registration completed but verification email failed. User can still login.'); + } else { + // In production, this might be more critical + console.error('๐Ÿšจ Critical: Verification email failed in production environment'); + } + } + return newUser; } @@ -63,6 +85,11 @@ class AuthService { throw new Error('Invalid email or password'); } + // Require email to be verified before allowing login + if (!user.email_verified) { + throw new Error('Please verify your email before logging in'); + } + // Verify password const isPasswordValid = await user.verifyPassword(password); if (!isPasswordValid) { @@ -94,6 +121,88 @@ class AuthService { }; } + // =============================== + // Email Verification + // =============================== + + generateRandomToken() { + return crypto.randomBytes(32).toString('hex'); + } + + hashDeterministic(value) { + return crypto.createHash('sha256').update(value).digest('hex'); + } + + async createEmailVerificationToken(userId) { + const rawToken = this.generateRandomToken(); + const tokenDigest = this.hashDeterministic(rawToken); + const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours + const query = ` + INSERT INTO email_verification_tokens (user_id, token_hash, expires_at) + VALUES ($1, $2, $3) + RETURNING id + `; + await database.query(query, [userId, tokenDigest, expiresAt]); + return rawToken; + } + + async sendVerificationEmail(user) { + const token = await this.createEmailVerificationToken(user.id); + const serviceBaseUrl = process.env.AUTH_PUBLIC_URL || `http://localhost:${process.env.PORT || 8011}`; + const verifyUrl = `${serviceBaseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`; + + const today = new Date(); + const dateString = today.toLocaleDateString('en-US'); + + const subject = 'Verify your email - Tech4biz'; + const text = `Hi ${user.first_name || user.username}, please verify your email by visiting: ${verifyUrl}`; + const html = ` +
+

๐Ÿ‘‹ Welcome to Codenuk, ${user.first_name || user.username}!

+

We're excited to have you on board ๐ŸŽ‰

+

Hi ${user.first_name || user.username}, thanks for registering with us on ${dateString}. + Please confirm your email address by clicking the button below:

+
+ Verify Email +
+

This link is valid for 24 hours.

+

If you didnโ€™t create this account, please ignore this email.

+
+ `; + + await sendMail(user.email, subject, text, html); + console.log(`โœ‰๏ธ Verification email sent to ${user.email}`); + } + + async verifyEmailToken(rawToken) { + if (!rawToken) { + throw new Error('Verification token is required'); + } + const tokenDigest = this.hashDeterministic(rawToken); + + return await database.transaction(async (client) => { + const findQuery = ` + SELECT * FROM email_verification_tokens + WHERE token_hash = $1 AND is_used = false AND expires_at > NOW() + ORDER BY created_at DESC + LIMIT 1 + `; + const found = await client.query(findQuery, [tokenDigest]); + if (found.rows.length === 0) { + throw new Error('Invalid or expired verification link'); + } + const record = found.rows[0]; + + // Mark user as verified + await client.query('UPDATE users SET email_verified = true, updated_at = NOW() WHERE id = $1', [record.user_id]); + + // Mark token as used + await client.query('UPDATE email_verification_tokens SET is_used = true, used_at = NOW() WHERE id = $1', [record.id]); + + return { userId: record.user_id }; + }); + } + // Refresh access token async refreshToken(refreshToken) { if (!refreshToken) { @@ -271,12 +380,9 @@ class AuthService { } // Hash token for storage - // async hashToken(token) { - // const saltRounds = 10; - // return await bcrypt.hash(token, saltRounds); - // } - hashToken(token) { - return crypto.createHash('sha256').update(token).digest('hex'); + async hashToken(token) { + const saltRounds = 10; + return await bcrypt.hash(token, saltRounds); } // Cleanup expired tokens and sessions diff --git a/services/user-auth/src/utils/email.js b/services/user-auth/src/utils/email.js new file mode 100644 index 0000000..551dbc9 --- /dev/null +++ b/services/user-auth/src/utils/email.js @@ -0,0 +1,170 @@ +const nodemailer = require('nodemailer'); +const path = require('path'); + +// Don't load .env here - load it lazily when needed +let _envLoaded = false; +let _envPath = null; + +const loadEnvIfNeeded = () => { + if (!_envLoaded) { + _envPath = path.join(__dirname, '../../../../.env'); // Go up 4 levels: utils -> src -> user-auth -> services -> automated-dev-pipeline + require('dotenv').config({ path: _envPath }); + _envLoaded = true; + console.log('๐Ÿ“ง Environment variables loaded from:', _envPath); + } +}; + +// Support env-configurable SMTP; fail if no valid configuration is provided +const createTransporter = () => { + // Load environment variables when this function is called + loadEnvIfNeeded(); + + const { + SMTP_HOST, + SMTP_PORT, + SMTP_SECURE, + SMTP_USER, + SMTP_PASS, + SMTP_FROM, + GMAIL_USER, + GMAIL_APP_PASSWORD, + } = process.env; + + console.log('๐Ÿ”ง Email configuration check:', { + SMTP_HOST: SMTP_HOST ? 'โœ“ Set' : 'โœ— Missing', + SMTP_USER: SMTP_USER ? 'โœ“ Set' : 'โœ— Missing', + SMTP_PASS: SMTP_PASS ? 'โœ“ Set' : 'โœ— Missing', + GMAIL_USER: GMAIL_USER ? 'โœ“ Set' : 'โœ— Missing', + GMAIL_APP_PASSWORD: GMAIL_APP_PASSWORD ? 'โœ“ Set' : 'โœ— Missing', + }); + + // Validate SMTP configuration + if (SMTP_HOST && SMTP_USER && SMTP_PASS) { + console.log('๐Ÿ“ง Using SMTP configuration'); + return nodemailer.createTransport({ + host: SMTP_HOST, + port: SMTP_PORT ? Number(SMTP_PORT) : 587, + secure: SMTP_SECURE ? SMTP_SECURE === 'true' : false, + auth: { user: SMTP_USER, pass: SMTP_PASS }, + socketTimeout: 300000, + connectionTimeout: 300000, + greetingTimeout: 300000, + }); + } + + // Validate Gmail configuration + if (GMAIL_USER && GMAIL_APP_PASSWORD) { + console.log('๐Ÿ“ง Using Gmail configuration'); + return nodemailer.createTransport({ + service: 'gmail', + auth: { + user: GMAIL_USER, + pass: GMAIL_APP_PASSWORD, + }, + socketTimeout: 300000, + connectionTimeout: 300000, + greetingTimeout: 300000, + }); + } + + // Fallback to mock transporter in development mode + if (process.env.NODE_ENV === 'development') { + console.warn('โš ๏ธ No email configuration found. Using mock transporter for development.'); + console.log('๐Ÿ“ง To enable real emails, set GMAIL_USER and GMAIL_APP_PASSWORD in the .env file.'); + return { + sendMail: async (mailOptions) => { + console.log('๐Ÿ“ง [MOCK] Email would be sent:', { + to: mailOptions.to, + subject: mailOptions.subject, + from: mailOptions.from, + }); + console.log('๐Ÿ“ง [MOCK] Email content:', mailOptions.text); + console.log('๐Ÿ“ง [MOCK] Email HTML:', mailOptions.html); + return { + messageId: 'mock-' + Date.now(), + response: 'Mock email sent successfully', + }; + }, + }; + } + + throw new Error( + `Email configuration is missing. Please set one of the following: + +Gmail Configuration: + GMAIL_USER=your-email@gmail.com + GMAIL_APP_PASSWORD=your-app-password + +Current environment: ${process.env.NODE_ENV || 'development'}` + ); +}; + +// Lazy transporter creation +let _transporter = null; + +const getTransporter = () => { + if (!_transporter) { + try { + _transporter = createTransporter(); + console.log('โœ… Email transporter created successfully'); + } catch (err) { + console.error('โŒ Failed to create email transporter:', err.message); + if (process.env.NODE_ENV === 'development') { + console.log('๐Ÿ”„ Creating mock transporter for development...'); + _transporter = { + sendMail: async (mailOptions) => { + console.log('๐Ÿ“ง [MOCK] Email would be sent:', { + to: mailOptions.to, + subject: mailOptions.subject, + from: mailOptions.from, + }); + return { + messageId: 'mock-' + Date.now(), + response: 'Mock email sent successfully', + }; + }, + }; + } else { + throw err; + } + } + } + return _transporter; +}; + +exports.sendMail = async (to, subject, text, html) => { + // Load environment variables when this function is called + loadEnvIfNeeded(); + + console.log('๐Ÿ” sendMail called with environment variables:'); + console.log(' SMTP_FROM:', process.env.SMTP_FROM); + console.log(' GMAIL_USER:', process.env.GMAIL_USER); + console.log(' NODE_ENV:', process.env.NODE_ENV); + + const fromAddress = process.env.SMTP_FROM || process.env.GMAIL_USER; + if (!fromAddress) { + console.error('โŒ No from address configured. Available env vars:', Object.keys(process.env).filter((k) => k.includes('SMTP') || k.includes('GMAIL'))); + throw new Error('No from address configured. Please set SMTP_FROM or GMAIL_USER.'); + } + + try { + const transporter = getTransporter(); + const info = await transporter.sendMail({ + from: fromAddress, + to, + subject, + text, + html, + }); + console.log(`โœ‰๏ธ Email sent to ${to}. MessageID: ${info.messageId}`); + return info; + } catch (err) { + console.error('โŒ Failed to send email:', { + message: err.message, + code: err.code, + response: err.response, + stack: err.stack, + }); + throw err; + } +}; \ No newline at end of file diff --git a/services/user-auth/test-email.js b/services/user-auth/test-email.js new file mode 100644 index 0000000..15d27be --- /dev/null +++ b/services/user-auth/test-email.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node + +require('dotenv').config(); +const { sendMail } = require('./src/utils/email'); + +async function testEmail() { + console.log('๐Ÿงช Testing Email Configuration'); + console.log('================================'); + + // Check environment variables + console.log('\n๐Ÿ”ง Environment Variables:'); + console.log('NODE_ENV:', process.env.NODE_ENV || 'development'); + console.log('SMTP_HOST:', process.env.SMTP_HOST || 'Not set'); + console.log('SMTP_USER:', process.env.SMTP_USER || 'Not set'); + console.log('SMTP_PASS:', process.env.SMTP_PASS ? '***Set***' : 'Not set'); + console.log('GMAIL_USER:', process.env.GMAIL_USER || 'Not set'); + console.log('GMAIL_APP_PASSWORD:', process.env.GMAIL_APP_PASSWORD ? '***Set***' : 'Not set'); + console.log('SMTP_FROM:', process.env.SMTP_FROM || 'Not set'); + + // Test email sending + console.log('\n๐Ÿ“ง Testing Email Sending...'); + + const testEmail = { + to: 'test@example.com', + subject: 'Test Email - User Auth Service', + text: 'This is a test email to verify email configuration.', + html: ` +
+

๐Ÿงช Test Email

+

This is a test email to verify email configuration for the User Auth Service.

+

Timestamp: ${new Date().toISOString()}

+

Environment: ${process.env.NODE_ENV || 'development'}

+
+

+ If you receive this email, your email configuration is working correctly! +

+
+ ` + }; + + try { + const result = await sendMail( + testEmail.to, + testEmail.subject, + testEmail.text, + testEmail.html + ); + + console.log('โœ… Email test successful!'); + console.log('Message ID:', result.messageId); + console.log('Response:', result.response); + + } catch (error) { + console.error('โŒ Email test failed:'); + console.error('Error:', error.message); + + if (error.code) { + console.error('Error Code:', error.code); + } + + if (error.response) { + console.error('SMTP Response:', error.response); + } + + console.error('\n๐Ÿ”ง Troubleshooting Tips:'); + console.error('1. Check your email credentials'); + console.error('2. Verify 2FA is enabled for Gmail'); + console.error('3. Use App Password, not regular password'); + console.error('4. Check firewall/network settings'); + console.error('5. Verify SMTP port (587 for Gmail)'); + } +} + +// Run the test +testEmail().catch(console.error);