# 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.