# Authentication System Documentation This document describes the authentication and authorization system implemented in the infoscreen_2025 project. ## Overview The system provides session-based authentication with role-based access control (RBAC). It includes: - **Backend**: Flask session-based auth with bcrypt password hashing - **Frontend**: React context/hooks for managing authentication state - **Permissions**: Decorators for protecting routes based on user roles - **Roles**: Four levels (user, editor, admin, superadmin) ## Architecture ### Backend Components #### 1. Auth Routes (`server/routes/auth.py`) Provides authentication endpoints: - **`POST /api/auth/login`** - Authenticate user and create session - **`POST /api/auth/logout`** - End user session - **`GET /api/auth/me`** - Get current user info (protected) - **`GET /api/auth/check`** - Quick auth status check #### 2. Permission Decorators (`server/permissions.py`) Decorators for protecting routes: ```python from server.permissions import require_role, admin_or_higher, editor_or_higher # Require specific role(s) @app.route('/admin-settings') @require_role('admin', 'superadmin') def admin_settings(): return "Admin only" # Convenience decorators @app.route('/settings') @admin_or_higher # admin or superadmin def settings(): return "Settings" @app.route('/events', methods=['POST']) @editor_or_higher # editor, admin, or superadmin def create_event(): return "Create event" ``` Available decorators: - `@require_auth` - Just require authentication - `@require_role(*roles)` - Require any of specified roles - `@superadmin_only` - Superadmin only - `@admin_or_higher` - Admin or superadmin - `@editor_or_higher` - Editor, admin, or superadmin #### 3. Session Configuration (`server/wsgi.py`) Flask session configured with: - Secret key from `FLASK_SECRET_KEY` environment variable - HTTPOnly cookies (prevent XSS) - SameSite=Lax (CSRF protection) - Secure flag in production (HTTPS only) ### Frontend Components #### 1. API Client (`dashboard/src/apiAuth.ts`) TypeScript functions for auth operations: ```typescript import { login, logout, fetchCurrentUser } from './apiAuth'; // Login await login('username', 'password'); // Get current user const user = await fetchCurrentUser(); // Logout await logout(); // Check auth status (lightweight) const { authenticated, role } = await checkAuth(); ``` Helper functions: ```typescript import { hasRole, hasAnyRole, isAdminOrHigher } from './apiAuth'; if (isAdminOrHigher(user)) { // Show admin UI } ``` #### 2. Auth Context/Hooks (`dashboard/src/useAuth.tsx`) React context for managing auth state: ```typescript import { useAuth, useCurrentUser, useIsAuthenticated } from './useAuth'; function MyComponent() { // Full auth context const { user, login, logout, loading, error, isAuthenticated } = useAuth(); // Or just what you need const user = useCurrentUser(); const isAuth = useIsAuthenticated(); if (loading) return
Loading...
; if (!isAuthenticated) { return ; } return
Welcome {user.username}!
; } ``` ## User Roles Four hierarchical roles with increasing permissions: | Role | Value | Description | Use Case | |------|-------|-------------|----------| | **User** | `user` | Read-only access | View events only | | **Editor** | `editor` | Can CRUD events/media | Content managers | | **Admin** | `admin` | Manage settings, users (except superadmin), groups | Organization staff | | **Superadmin** | `superadmin` | Full system access | Developers, system admins | ### Permission Matrix | Action | User | Editor | Admin | Superadmin | |--------|------|--------|-------|------------| | View events | ✅ | ✅ | ✅ | ✅ | | Create/edit events | ❌ | ✅ | ✅ | ✅ | | Manage media | ❌ | ✅ | ✅ | ✅ | | Manage groups/clients | ❌ | ❌ | ✅ | ✅ | | Manage users (non-superadmin) | ❌ | ❌ | ✅ | ✅ | | Manage settings | ❌ | ❌ | ✅ | ✅ | | Manage superadmins | ❌ | ❌ | ❌ | ✅ | | System configuration | ❌ | ❌ | ❌ | ✅ | ## Setup Instructions ### 1. Environment Configuration Add to your `.env` file: ```bash # Flask session secret key (REQUIRED) # Generate with: python -c 'import secrets; print(secrets.token_hex(32))' FLASK_SECRET_KEY=your_secret_key_here # Superadmin account (REQUIRED for initial setup) DEFAULT_SUPERADMIN_USERNAME=superadmin DEFAULT_SUPERADMIN_PASSWORD=your_secure_password ``` ### 2. Database Initialization The superadmin user is created automatically when containers start. See `SUPERADMIN_SETUP.md` for details. ### 3. Frontend Integration Wrap your app with `AuthProvider` in `main.tsx` or `App.tsx`: ```typescript import { AuthProvider } from './useAuth'; function App() { return ( {/* Your app components */} ); } ``` ## Usage Examples ### Backend: Protecting Routes ```python from flask import Blueprint from server.permissions import require_role, admin_or_higher users_bp = Blueprint("users", __name__, url_prefix="/api/users") @users_bp.route("", methods=["GET"]) @admin_or_higher def list_users(): """List all users - admin+ only""" # Implementation pass @users_bp.route("", methods=["POST"]) @require_role('superadmin') def create_superadmin(): """Create superadmin - superadmin only""" # Implementation pass ``` ### Frontend: Conditional Rendering ```typescript import { useAuth } from './useAuth'; import { isAdminOrHigher, isEditorOrHigher } from './apiAuth'; function NavigationMenu() { const { user } = useAuth(); return ( ); } ``` ### Frontend: Login Form Example ```typescript import { useState } from 'react'; import { useAuth } from './useAuth'; function LoginPage() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const { login, loading, error } = useAuth(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await login(username, password); // Redirect on success window.location.href = '/dashboard'; } catch (err) { // Error is already in auth context console.error('Login failed:', err); } }; return (

Login

{error &&
{error}
} setUsername(e.target.value)} disabled={loading} /> setPassword(e.target.value)} disabled={loading} />
); } ``` ## Security Considerations ### Backend Security 1. **Password Hashing**: All passwords hashed with bcrypt (salt rounds default) 2. **Session Security**: - HTTPOnly cookies (prevent XSS access) - SameSite=Lax (CSRF protection) - Secure flag in production (HTTPS only) 3. **Secret Key**: Must be set via environment variable, not hardcoded 4. **Role Checking**: Server-side validation on every protected route ### Frontend Security 1. **Credentials**: Always use `credentials: 'include'` in fetch calls 2. **No Password Storage**: Never store passwords in localStorage/sessionStorage 3. **Role Gating**: UI gating is convenience, not security (always validate server-side) 4. **HTTPS**: Always use HTTPS in production ### Production Checklist - [ ] Generate strong `FLASK_SECRET_KEY` (32+ bytes) - [ ] Set `SESSION_COOKIE_SECURE=True` (handled automatically by ENV=production) - [ ] Use HTTPS with valid TLS certificate - [ ] Change default superadmin password after first login - [ ] Review and audit user roles regularly - [ ] Enable audit logging (future enhancement) ## API Reference ### Authentication Endpoints #### POST /api/auth/login Authenticate user and create session. **Request:** ```json { "username": "string", "password": "string" } ``` **Response (200):** ```json { "message": "Login successful", "user": { "id": 1, "username": "admin", "role": "admin" } } ``` **Errors:** - `400` - Missing username or password - `401` - Invalid credentials or account disabled #### POST /api/auth/logout End current session. **Response (200):** ```json { "message": "Logout successful" } ``` #### GET /api/auth/me Get current user information (requires authentication). **Response (200):** ```json { "id": 1, "username": "admin", "role": "admin", "is_active": true } ``` **Errors:** - `401` - Not authenticated or account disabled #### GET /api/auth/check Quick authentication status check. **Response (200):** ```json { "authenticated": true, "role": "admin" } ``` Or if not authenticated: ```json { "authenticated": false } ``` ## Testing ### Manual Testing 1. **Create test users** (via database or future user management UI): ```sql INSERT INTO users (username, password_hash, role, is_active) VALUES ('testuser', '', 'user', 1); ``` 2. **Test login**: ```bash curl -X POST http://localhost:8000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"superadmin","password":"your_password"}' \ -c cookies.txt ``` 3. **Test /me endpoint**: ```bash curl http://localhost:8000/api/auth/me -b cookies.txt ``` 4. **Test protected route**: ```bash # Should fail without auth curl http://localhost:8000/api/protected # Should work with cookie curl http://localhost:8000/api/protected -b cookies.txt ``` ### Automated Testing Example test cases (to be implemented): ```python def test_login_success(): response = client.post('/api/auth/login', json={ 'username': 'testuser', 'password': 'testpass' }) assert response.status_code == 200 assert 'user' in response.json def test_login_invalid_credentials(): response = client.post('/api/auth/login', json={ 'username': 'testuser', 'password': 'wrongpass' }) assert response.status_code == 401 def test_me_authenticated(): # Login first client.post('/api/auth/login', json={'username': 'testuser', 'password': 'testpass'}) response = client.get('/api/auth/me') assert response.status_code == 200 assert response.json['username'] == 'testuser' def test_me_not_authenticated(): response = client.get('/api/auth/me') assert response.status_code == 401 ``` ## Troubleshooting ### Login Not Working **Symptoms**: Login endpoint returns 401 even with correct credentials **Solutions**: 1. Verify user exists in database: `SELECT * FROM users WHERE username='...'` 2. Check password hash is valid bcrypt format 3. Verify user `is_active=1` 4. Check server logs for bcrypt errors ### Session Not Persisting **Symptoms**: `/api/auth/me` returns 401 after successful login **Solutions**: 1. Verify `FLASK_SECRET_KEY` is set 2. Check frontend is sending `credentials: 'include'` in fetch 3. Verify cookies are being set (check browser DevTools) 4. Check CORS settings if frontend/backend on different domains ### Permission Denied on Protected Route **Symptoms**: 403 error on decorated routes **Solutions**: 1. Verify user is logged in (`/api/auth/me`) 2. Check user role matches required role 3. Verify decorator is applied correctly 4. Check session hasn't expired ### TypeScript Errors in Frontend **Symptoms**: Type errors when using auth hooks **Solutions**: 1. Ensure `AuthProvider` is wrapping your app 2. Import types correctly: `import type { User } from './apiAuth'` 3. Check TypeScript config for `verbatimModuleSyntax` ## Next Steps See `userrole-management.md` for the complete implementation roadmap: 1. ✅ **Extend User Model** - Done 2. ✅ **Seed Superadmin** - Done (`init_defaults.py`) 3. ✅ **Expose Current User Role** - Done (this document) 4. ⏳ **Implement Minimal Role Enforcement** - Apply decorators to existing routes 5. ⏳ **Test the Flow** - Verify permissions work correctly 6. ⏳ **Frontend Role Gating** - Update UI components 7. ⏳ **User Management UI** - Build admin interface ## References - User model: `models/models.py` - Auth routes: `server/routes/auth.py` - Permissions: `server/permissions.py` - API client: `dashboard/src/apiAuth.ts` - Auth context: `dashboard/src/useAuth.tsx` - Flask sessions: https://flask.palletsprojects.com/en/latest/api/#sessions - Bcrypt: https://pypi.org/project/bcrypt/