diff --git a/.env.production b/.env.production index d99fba9..090a4d2 100644 --- a/.env.production +++ b/.env.production @@ -9,6 +9,10 @@ MONGODB_DATABASE=scoffer # JWT Secret for Backend (CHANGE THIS IN PRODUCTION!) JWT_SECRET=your-super-secret-jwt-key-change-this-in-production +# Registration Control +# Set to 'false' to disable user registration (recommended for production) +ALLOW_REGISTRATION=false + # Port Configuration (customize as needed for your server) FRONTEND_PORT=3000 BACKEND_PORT=5000 @@ -21,8 +25,5 @@ MONGO_EXPRESS_PORT=8081 # Replace 'your-domain.com' with your actual domain REACT_APP_API_URL=https://your-domain.com/api -# Alternative: If backend runs on different port/subdomain -# REACT_APP_API_URL=https://api.your-domain.com - # For local testing with containers (no SSL) # REACT_APP_API_URL=http://localhost:5000/api diff --git a/REGISTRATION_CONTROL.md b/REGISTRATION_CONTROL.md new file mode 100644 index 0000000..50383ce --- /dev/null +++ b/REGISTRATION_CONTROL.md @@ -0,0 +1,281 @@ +# Registration Control Guide + +## Overview + +The application now supports controlling user registration through an environment variable. This is a critical security feature that allows administrators to disable new user registrations in production environments while keeping existing user login functionality intact. + +## Environment Variable + +| Variable | Default | Description | +|----------|---------|-------------| +| `ALLOW_REGISTRATION` | `true` | Controls whether new user registration is allowed | + +## Configuration + +### Enable Registration (Default) +```bash +ALLOW_REGISTRATION=true +``` +or simply omit the variable (defaults to `true`) + +### Disable Registration +```bash +ALLOW_REGISTRATION=false +``` + +## Usage Scenarios + +### Development Environment +**Recommended**: Enable registration for testing +```bash +# .env or .env.local +ALLOW_REGISTRATION=true +``` + +### Production Environment +**Recommended**: Disable registration after initial setup +```bash +# .env.production +ALLOW_REGISTRATION=false +``` + +### Staging Environment +**Optional**: Enable for testing, disable for demos +```bash +# .env.staging +ALLOW_REGISTRATION=true # or false depending on needs +``` + +## Implementation Details + +### Backend Behavior + +**When `ALLOW_REGISTRATION=true` (default)**: +- `POST /api/users/register` accepts new registrations +- Returns 201 with user data and JWT token on success +- Returns 400 if user already exists + +**When `ALLOW_REGISTRATION=false`**: +- `POST /api/users/register` returns 403 Forbidden +- Response: `{"error": "Registration is currently disabled"}` +- Login functionality remains unaffected + +### Frontend Considerations + +The frontend should handle the 403 response gracefully: + +```javascript +// Example error handling in registration form +try { + const response = await api.post('/users/register', userData); + // Handle success +} catch (error) { + if (error.response?.status === 403) { + // Registration disabled + setError('Registration is currently disabled. Please contact an administrator.'); + } else { + // Other errors (validation, user exists, etc.) + setError(error.response?.data?.error || 'Registration failed'); + } +} +``` + +## Deployment Examples + +### Quick Setup Commands + +**Enable registration**: +```bash +echo "ALLOW_REGISTRATION=true" >> .env +docker-compose up -d --build +``` + +**Disable registration**: +```bash +echo "ALLOW_REGISTRATION=false" >> .env +docker-compose up -d --build +``` + +**Toggle without rebuild** (if container is already running): +```bash +# Update .env file +sed -i 's/ALLOW_REGISTRATION=.*/ALLOW_REGISTRATION=false/' .env + +# Restart backend container to pick up new env var +docker-compose restart backend +``` + +### Environment-Specific Deployments + +**Development with registration enabled**: +```bash +# .env.dev +ALLOW_REGISTRATION=true +REACT_APP_API_URL=http://localhost:5000/api +JWT_SECRET=dev-secret-key + +# Deploy +cp .env.dev .env +docker-compose up -d +``` + +**Production with registration disabled**: +```bash +# .env.prod +ALLOW_REGISTRATION=false +REACT_APP_API_URL=https://your-domain.com/api +JWT_SECRET=super-secure-production-key + +# Deploy +cp .env.prod .env +docker-compose up -d --build +``` + +## Security Best Practices + +### 1. Disable Registration in Production +```bash +# Always set this in production +ALLOW_REGISTRATION=false +``` + +### 2. Create Initial Admin User +Before disabling registration, ensure you have at least one admin user: + +```bash +# Method 1: Enable registration temporarily +ALLOW_REGISTRATION=true docker-compose up -d +# Register your admin user via frontend/API +# Then disable registration +ALLOW_REGISTRATION=false docker-compose restart backend + +# Method 2: Create user directly in database (advanced) +docker-compose exec mongodb mongosh scoffer +# Use MongoDB commands to create user +``` + +### 3. Monitor Registration Attempts +Consider adding logging for blocked registration attempts: + +```javascript +// In backend/routes/users.js (optional enhancement) +if (!registrationEnabled) { + console.log(`Registration attempt blocked from IP: ${req.ip}`); + return res.status(403).json({ + error: 'Registration is currently disabled' + }); +} +``` + +## Troubleshooting + +### Registration Still Working After Disabling +**Cause**: Environment variable not picked up by container + +**Solutions**: +1. Restart the backend container: + ```bash + docker-compose restart backend + ``` + +2. Rebuild if env var was added after initial build: + ```bash + docker-compose up -d --build backend + ``` + +3. Verify environment variable in container: + ```bash + docker-compose exec backend env | grep ALLOW_REGISTRATION + ``` + +### Frontend Shows Registration Form When Disabled +**Cause**: Frontend doesn't know registration is disabled until API call + +**Solutions**: +1. Add API endpoint to check registration status: + ```javascript + // GET /api/users/registration-status + router.get('/registration-status', (req, res) => { + res.json({ + enabled: process.env.ALLOW_REGISTRATION !== 'false' + }); + }); + ``` + +2. Hide registration form based on API response +3. Handle 403 error gracefully in registration form + +### Users Can't Login After Disabling Registration +**Issue**: This should NOT happen - login is separate from registration + +**Check**: +1. Verify `ALLOW_REGISTRATION` only affects `/register` endpoint +2. Ensure `/login` endpoint is unaffected +3. Check JWT_SECRET hasn't changed + +## API Reference + +### Registration Endpoint + +**Endpoint**: `POST /api/users/register` + +**When Enabled** (`ALLOW_REGISTRATION=true`): +```bash +curl -X POST http://localhost:5000/api/users/register \ + -H "Content-Type: application/json" \ + -d '{"username":"test","email":"test@example.com","password":"password123"}' + +# Response: 201 Created +{ + "message": "User created successfully", + "token": "jwt-token-here", + "user": { + "id": "user-id", + "username": "test", + "email": "test@example.com" + } +} +``` + +**When Disabled** (`ALLOW_REGISTRATION=false`): +```bash +curl -X POST http://localhost:5000/api/users/register \ + -H "Content-Type: application/json" \ + -d '{"username":"test","email":"test@example.com","password":"password123"}' + +# Response: 403 Forbidden +{ + "error": "Registration is currently disabled" +} +``` + +### Login Endpoint (Unaffected) + +**Endpoint**: `POST /api/users/login` + +```bash +curl -X POST http://localhost:5000/api/users/login \ + -H "Content-Type: application/json" \ + -d '{"email":"existing@example.com","password":"password123"}' + +# Response: 200 OK (regardless of ALLOW_REGISTRATION setting) +{ + "message": "Login successful", + "token": "jwt-token-here", + "user": { + "id": "user-id", + "username": "existing", + "email": "existing@example.com" + } +} +``` + +## Quick Reference + +| Action | Command | +|--------|---------| +| Enable registration | `ALLOW_REGISTRATION=true` | +| Disable registration | `ALLOW_REGISTRATION=false` | +| Check current setting | `docker-compose exec backend env \| grep ALLOW_REGISTRATION` | +| Restart backend | `docker-compose restart backend` | +| Test registration | `curl -X POST localhost:5000/api/users/register -H "Content-Type: application/json" -d '{"username":"test","email":"test@test.com","password":"test123"}'` | diff --git a/backend/routes/users.js b/backend/routes/users.js index e9d1a95..420b4ec 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -6,6 +6,14 @@ const User = require('../models/User'); // Register new user router.post('/register', async (req, res) => { try { + // Check if registration is enabled + const registrationEnabled = process.env.ALLOW_REGISTRATION !== 'false'; + if (!registrationEnabled) { + return res.status(403).json({ + error: 'Registration is currently disabled' + }); + } + const { username, email, password } = req.body; // Check if user already exists diff --git a/docker-compose.yml b/docker-compose.yml index e2b9190..ef331a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,7 @@ services: - MONGODB_PORT=27017 - MONGODB_DATABASE=scoffer - JWT_SECRET=${JWT_SECRET} + - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true} ports: - "${BACKEND_PORT:-5000}:5000" depends_on: