
- Updated `.env` and added `.env.test` for environment variables. - Introduced API documentation in `API_DOCUMENTATION.md`. - Added authentication setup guide in `AUTHENTICATION_SETUP.md`. - Implemented user authentication with JWT and email verification. - Created new routes for user management and form submissions. - Added middleware for API key authentication and error handling. - Set up Redis for rate limiting and notifications. - Removed obsolete files and configurations related to the previous Rust implementation.
433 lines
11 KiB
Markdown
433 lines
11 KiB
Markdown
# Authentication System Setup Guide
|
|
|
|
## Overview
|
|
|
|
This guide will help you set up the robust user authentication and authorization system for your Formies SaaS application. The system includes:
|
|
|
|
- **JWT-based authentication** with access and refresh tokens
|
|
- **Email verification** with automated emails
|
|
- **Password reset** functionality
|
|
- **Role-based authorization** (user, admin, super_admin)
|
|
- **Account security** features (failed login tracking, account locking)
|
|
- **Rate limiting** to prevent abuse
|
|
- **Session management** with token blacklisting
|
|
|
|
## Required Dependencies
|
|
|
|
The following packages have been added to your `package.json`:
|
|
|
|
```json
|
|
{
|
|
"bcryptjs": "^2.4.3",
|
|
"express-rate-limit": "^7.1.5",
|
|
"express-session": "^1.17.3",
|
|
"express-validator": "^7.0.1",
|
|
"jsonwebtoken": "^9.0.2",
|
|
"nodemailer": "^6.9.8",
|
|
"passport": "^0.7.0",
|
|
"passport-jwt": "^4.0.1",
|
|
"passport-local": "^1.0.0"
|
|
}
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
Create a `.env` file with the following variables:
|
|
|
|
```env
|
|
# Database Configuration
|
|
DB_HOST=localhost
|
|
DB_USER=your_db_user
|
|
DB_PASSWORD=your_db_password
|
|
DB_NAME=forms_db
|
|
|
|
# JWT Configuration (REQUIRED)
|
|
JWT_SECRET=your-super-secret-jwt-key-at-least-32-characters-long
|
|
JWT_ISSUER=formies
|
|
JWT_AUDIENCE=formies-users
|
|
JWT_ACCESS_EXPIRY=15m
|
|
JWT_REFRESH_EXPIRY=7d
|
|
|
|
# Session Configuration
|
|
SESSION_SECRET=your-session-secret-key-change-this-in-production
|
|
|
|
# Application Configuration
|
|
APP_URL=http://localhost:3000
|
|
NODE_ENV=development
|
|
PORT=3000
|
|
|
|
# SMTP Email Configuration (Optional but recommended)
|
|
SMTP_HOST=smtp.gmail.com
|
|
SMTP_PORT=587
|
|
SMTP_SECURE=false
|
|
SMTP_USER=your-email@gmail.com
|
|
SMTP_PASS=your-app-password
|
|
SMTP_FROM_EMAIL=noreply@yourdomain.com
|
|
|
|
# Notification Configuration
|
|
NTFY_ENABLED=true
|
|
NTFY_TOPIC_URL=https://ntfy.sh/your-topic
|
|
```
|
|
|
|
## Database Setup
|
|
|
|
1. **Install dependencies:**
|
|
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
2. **Update your database** by running the updated `init.sql`:
|
|
|
|
This script will create all necessary tables, including the `users` table with a default `super_admin` account (`admin@formies.local`).
|
|
The initial password for this `super_admin` is NOT set in the `init.sql` script. The `must_change_password` flag will be set to `TRUE`.
|
|
|
|
```bash
|
|
# If using Docker
|
|
docker-compose down
|
|
docker-compose up -d
|
|
|
|
# Or manually run the SQL file in your MySQL database
|
|
mysql -u your_user -p your_database < init.sql
|
|
```
|
|
|
|
If the login is for the `super_admin` (`admin@formies.local`) and it's their first login (`must_change_password` is `TRUE` on the user object returned from the `/login` attempt, even if successful), the API might return a successful login response but the client should check for this flag. Alternatively, the `/login` endpoint itself has been modified to return a `403 Forbidden` response with `code: "MUST_CHANGE_PASSWORD"` directly if this condition is met. The client application should handle this response and prompt the user to use the `/force-change-password` endpoint.
|
|
|
|
## API Endpoints
|
|
|
|
### Authentication Endpoints
|
|
|
|
All authentication endpoints are prefixed with `/api/auth`:
|
|
|
|
#### Registration
|
|
|
|
```http
|
|
POST /api/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "John",
|
|
"last_name": "Doe"
|
|
}
|
|
```
|
|
|
|
#### Login
|
|
|
|
```http
|
|
POST /api/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecurePass123!"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"message": "Login successful",
|
|
"data": {
|
|
"user": { ... },
|
|
"accessToken": "eyJ...",
|
|
"refreshToken": "eyJ...",
|
|
"accessTokenExpiresAt": "2024-01-01T00:00:00.000Z",
|
|
"refreshTokenExpiresAt": "2024-01-07T00:00:00.000Z",
|
|
"tokenType": "Bearer"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Super Admin First Login:**
|
|
|
|
If the login attempt is for the `super_admin` (`admin@formies.local`) and the `must_change_password` flag is `TRUE` for this user, the `/api/auth/login` endpoint will return a `403 Forbidden` response with the following structure:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Password change required.",
|
|
"code": "MUST_CHANGE_PASSWORD",
|
|
"data": {
|
|
"user": {
|
|
"id": "user_id",
|
|
"uuid": "user_uuid",
|
|
"email": "admin@formies.local",
|
|
"role": "super_admin"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The client application should detect this `code: "MUST_CHANGE_PASSWORD"` and guide the user to set a new password using the endpoint below. The `accessToken` and `refreshToken` will NOT be issued in this case. The client will need to make a subsequent call to `/api/auth/force-change-password` using a temporary mechanism if required, or by having the user log in, get the 403, then use a password change form that calls the next endpoint. For the current implementation, the super_admin will receive a standard JWT upon providing correct credentials (even if `must_change_password` is true), and this token should be used for the `/force-change-password` call.
|
|
|
|
#### Force Password Change
|
|
|
|
This endpoint is used when a user, particularly the initial `super_admin`, needs to set their password for the first time or has been flagged for a mandatory password update.
|
|
|
|
```http
|
|
POST /api/auth/force-change-password
|
|
Authorization: Bearer your-access-token-from-login-attempt
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"newPassword": "ANewStrongPassword123!"
|
|
}
|
|
|
|
Response (on success):
|
|
{
|
|
"success": true,
|
|
"message": "Password changed successfully. Please log in again with your new password."
|
|
}
|
|
```
|
|
|
|
After a successful password change using this endpoint:
|
|
|
|
- The user's password is updated.
|
|
- The `must_change_password` flag is set to `FALSE`.
|
|
- All other active sessions for this user are invalidated for security.
|
|
- The user will need to log in again with their new password to obtain new session tokens.
|
|
|
|
#### Token Refresh
|
|
|
|
```http
|
|
POST /api/auth/refresh
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"refreshToken": "eyJ..."
|
|
}
|
|
```
|
|
|
|
#### Logout
|
|
|
|
```http
|
|
POST /api/auth/logout
|
|
Authorization: Bearer your-access-token
|
|
```
|
|
|
|
#### Email Verification
|
|
|
|
```http
|
|
GET /api/auth/verify-email?token=verification_token
|
|
```
|
|
|
|
#### Profile Management
|
|
|
|
```http
|
|
GET /api/auth/profile
|
|
Authorization: Bearer your-access-token
|
|
|
|
PUT /api/auth/profile
|
|
Authorization: Bearer your-access-token
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"first_name": "John",
|
|
"last_name": "Doe",
|
|
"email": "newemail@example.com"
|
|
}
|
|
```
|
|
|
|
## Security Features
|
|
|
|
### Password Requirements
|
|
|
|
- Minimum 8 characters
|
|
- At least one lowercase letter
|
|
- At least one uppercase letter
|
|
- At least one number
|
|
- At least one special character (@$!%\*?&)
|
|
|
|
### Account Security
|
|
|
|
- Failed login attempts are tracked
|
|
- Account locks after 5 failed attempts for 30 minutes
|
|
- Email verification required for new accounts
|
|
- JWT tokens are tracked and can be revoked
|
|
|
|
### Rate Limiting
|
|
|
|
- **Login attempts:** 5 per 15 minutes per IP/email
|
|
- **Registration:** 3 per hour per IP
|
|
- **Password reset:** 3 per hour per IP/email
|
|
|
|
## Using the Authentication System
|
|
|
|
### Frontend Integration
|
|
|
|
1. **Store tokens securely:**
|
|
|
|
```javascript
|
|
// Store in secure httpOnly cookies or localStorage (less secure)
|
|
localStorage.setItem("accessToken", response.data.accessToken);
|
|
localStorage.setItem("refreshToken", response.data.refreshToken);
|
|
```
|
|
|
|
2. **Include token in requests:**
|
|
|
|
```javascript
|
|
fetch("/api/protected-endpoint", {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
```
|
|
|
|
3. **Handle token refresh:**
|
|
|
|
```javascript
|
|
async function refreshToken() {
|
|
const refreshToken = localStorage.getItem("refreshToken");
|
|
const response = await fetch("/api/auth/refresh", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ refreshToken }),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
localStorage.setItem("accessToken", data.data.accessToken);
|
|
return data.data.accessToken;
|
|
} else {
|
|
// Redirect to login
|
|
localStorage.removeItem("accessToken");
|
|
localStorage.removeItem("refreshToken");
|
|
window.location.href = "/login";
|
|
}
|
|
}
|
|
```
|
|
|
|
### Backend Integration
|
|
|
|
1. **Protect routes with authentication:**
|
|
|
|
```javascript
|
|
const {
|
|
requireAuth,
|
|
requireAdmin,
|
|
} = require("./src/middleware/authMiddleware");
|
|
|
|
// Require authentication
|
|
router.get("/protected", requireAuth, (req, res) => {
|
|
res.json({ user: req.user });
|
|
});
|
|
|
|
// Require admin role
|
|
router.get("/admin-only", requireAdmin, (req, res) => {
|
|
res.json({ message: "Admin access granted" });
|
|
});
|
|
```
|
|
|
|
2. **Check resource ownership:**
|
|
|
|
```javascript
|
|
const {
|
|
requireOwnershipOrAdmin,
|
|
} = require("./src/middleware/authMiddleware");
|
|
|
|
router.get(
|
|
"/forms/:id",
|
|
requireOwnershipOrAdmin(async (req) => {
|
|
const form = await Form.findById(req.params.id);
|
|
return form.user_id;
|
|
}),
|
|
(req, res) => {
|
|
// User can only access their own forms or admin can access all
|
|
}
|
|
);
|
|
```
|
|
|
|
## Migration from Basic Auth
|
|
|
|
The system maintains backward compatibility with your existing basic auth. To fully migrate:
|
|
|
|
1. **Update admin routes** to use the new authentication system
|
|
2. **Create admin users** in the database with appropriate roles
|
|
3. **Remove basic auth middleware** once migration is complete
|
|
|
|
## Default Admin Account
|
|
|
|
A default super admin account is created automatically:
|
|
|
|
- **Email:** admin@formies.local
|
|
- **Password:** admin123 (change immediately!)
|
|
|
|
## Email Configuration
|
|
|
|
For email verification and password reset to work, configure SMTP settings:
|
|
|
|
### Gmail Setup
|
|
|
|
1. Enable 2-factor authentication
|
|
2. Generate an app password
|
|
3. Use the app password in `SMTP_PASS`
|
|
|
|
### Other Providers
|
|
|
|
- **Outlook:** smtp-mail.outlook.com:587
|
|
- **SendGrid:** smtp.sendgrid.net:587
|
|
- **Mailgun:** smtp.mailgun.org:587
|
|
|
|
## Production Considerations
|
|
|
|
1. **Use strong secrets:** Generate random JWT_SECRET and SESSION_SECRET
|
|
2. **Enable HTTPS:** Set `NODE_ENV=production` and use SSL certificates
|
|
3. **Use Redis for sessions:** Replace memory sessions with Redis
|
|
4. **Monitor rate limits:** Adjust rate limiting based on usage patterns
|
|
5. **Backup token sessions:** Consider database-backed session storage
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **JWT_SECRET not set:**
|
|
|
|
```
|
|
WARNING: JWT_SECRET not set. Authentication will not work properly.
|
|
```
|
|
|
|
Solution: Add JWT_SECRET to your .env file
|
|
|
|
2. **Email service not working:**
|
|
|
|
```
|
|
Email service not configured. Set SMTP environment variables.
|
|
```
|
|
|
|
Solution: Configure SMTP settings in .env file
|
|
|
|
3. **Database connection errors:**
|
|
|
|
- Verify database credentials
|
|
- Ensure database exists
|
|
- Check if init.sql has been run
|
|
|
|
4. **Token validation errors:**
|
|
- Check if JWT_SECRET matches between requests
|
|
- Verify token hasn't expired
|
|
- Ensure token is properly formatted in Authorization header
|
|
|
|
## Testing the System
|
|
|
|
Use these curl commands to test the authentication endpoints:
|
|
|
|
```bash
|
|
# Register a new user
|
|
curl -X POST http://localhost:3000/api/auth/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"test@example.com","password":"TestPass123!","first_name":"Test","last_name":"User"}'
|
|
|
|
# Login
|
|
curl -X POST http://localhost:3000/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"test@example.com","password":"TestPass123!"}'
|
|
|
|
# Access protected endpoint
|
|
curl -X GET http://localhost:3000/api/auth/profile \
|
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
|
|
```
|
|
|
|
This authentication system provides enterprise-grade security for your SaaS application while maintaining flexibility and ease of use.
|