Updated backend with api implementation
This commit is contained in:
parent
2fc8c9c960
commit
2e972f1157
66
.env.backup
Normal file
66
.env.backup
Normal file
@ -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
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
|
||||
181
services/user-auth/README-EMAIL-SETUP.md
Normal file
181
services/user-auth/README-EMAIL-SETUP.md
Normal file
@ -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: <message-id>
|
||||
```
|
||||
|
||||
**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', '<h1>Test</h1>')
|
||||
.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
|
||||
38
services/user-auth/env.example
Normal file
38
services/user-auth/env.example
Normal file
@ -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
|
||||
124
services/user-auth/package-lock.json
generated
124
services/user-auth/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
88
services/user-auth/setup-email.sh
Executable file
88
services/user-auth/setup-email.sh
Executable file
@ -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!"
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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';
|
||||
@ -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;
|
||||
}
|
||||
|
||||
console.log('📄 Running migration: 001_user_auth_schema.sql');
|
||||
const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
|
||||
console.log(`📄 Running migration: ${migrationFile}`);
|
||||
|
||||
// Execute the migration
|
||||
await database.query(migrationSQL);
|
||||
await database.query(migrationSQL);
|
||||
console.log(`✅ Migration ${migrationFile} completed!`);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 = `
|
||||
<div style="font-family:Arial,Helvetica,sans-serif;max-width:640px;margin:0 auto;padding:24px;border:1px solid #eee;border-radius:8px;">
|
||||
<h2>👋 Welcome to Codenuk, ${user.first_name || user.username}!</h2>
|
||||
<p>We're excited to have you on board 🎉</p>
|
||||
<p>Hi <b>${user.first_name || user.username}</b>, thanks for registering with us on <b>${dateString}</b>.
|
||||
Please confirm your email address by clicking the button below:</p>
|
||||
<div style="text-align:center;margin:24px 0;">
|
||||
<a href="${verifyUrl}" style="background:#1677ff;color:#fff;text-decoration:none;padding:12px 20px;border-radius:6px;display:inline-block;font-weight:600;">Verify Email</a>
|
||||
</div>
|
||||
<p>This link is valid for <b>24 hours</b>.</p>
|
||||
<p>If you didn’t create this account, please ignore this email.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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
|
||||
|
||||
170
services/user-auth/src/utils/email.js
Normal file
170
services/user-auth/src/utils/email.js
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
75
services/user-auth/test-email.js
Normal file
75
services/user-auth/test-email.js
Normal file
@ -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: `
|
||||
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
||||
<h2>🧪 Test Email</h2>
|
||||
<p>This is a test email to verify email configuration for the User Auth Service.</p>
|
||||
<p><strong>Timestamp:</strong> ${new Date().toISOString()}</p>
|
||||
<p><strong>Environment:</strong> ${process.env.NODE_ENV || 'development'}</p>
|
||||
<hr>
|
||||
<p style="color: #666; font-size: 12px;">
|
||||
If you receive this email, your email configuration is working correctly!
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
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);
|
||||
Loading…
Reference in New Issue
Block a user