
- 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.
11 KiB
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
:
{
"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:
# 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
-
Install dependencies:
npm install
-
Update your database by running the updated
init.sql
:This script will create all necessary tables, including the
users
table with a defaultsuper_admin
account (admin@formies.local
). The initial password for thissuper_admin
is NOT set in theinit.sql
script. Themust_change_password
flag will be set toTRUE
.# 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
isTRUE
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 a403 Forbidden
response withcode: "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
POST /api/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe"
}
Login
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:
{
"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.
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 toFALSE
. - 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
POST /api/auth/refresh
Content-Type: application/json
{
"refreshToken": "eyJ..."
}
Logout
POST /api/auth/logout
Authorization: Bearer your-access-token
Email Verification
GET /api/auth/verify-email?token=verification_token
Profile Management
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
-
Store tokens securely:
// Store in secure httpOnly cookies or localStorage (less secure) localStorage.setItem("accessToken", response.data.accessToken); localStorage.setItem("refreshToken", response.data.refreshToken);
-
Include token in requests:
fetch("/api/protected-endpoint", { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, });
-
Handle token refresh:
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
-
Protect routes with authentication:
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" }); });
-
Check resource ownership:
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:
- Update admin routes to use the new authentication system
- Create admin users in the database with appropriate roles
- 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
- Enable 2-factor authentication
- Generate an app password
- 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
- Use strong secrets: Generate random JWT_SECRET and SESSION_SECRET
- Enable HTTPS: Set
NODE_ENV=production
and use SSL certificates - Use Redis for sessions: Replace memory sessions with Redis
- Monitor rate limits: Adjust rate limiting based on usage patterns
- Backup token sessions: Consider database-backed session storage
Troubleshooting
Common Issues
-
JWT_SECRET not set:
WARNING: JWT_SECRET not set. Authentication will not work properly.
Solution: Add JWT_SECRET to your .env file
-
Email service not working:
Email service not configured. Set SMTP environment variables.
Solution: Configure SMTP settings in .env file
-
Database connection errors:
- Verify database credentials
- Ensure database exists
- Check if init.sql has been run
-
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:
# 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.