diff --git a/backend/main.py b/backend/main.py index c030c67..4ee5e3f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -110,16 +110,35 @@ def check_docker_availability(): # ✅ ENHANCED: Flask app configuration with your .env variables app = Flask(__name__) +def get_required_secret(env_var: str, description: str) -> str: + """Get required secret from environment, raise error if not set""" + value = os.getenv(env_var) + if not value: + raise ValueError(f"{description} ({env_var}) must be set in environment variables for security. Do not use default values for secrets.") + return value + +# Validate required secrets at startup +try: + _secret_key = get_required_secret('SECRET_KEY', 'Flask secret key') + _jwt_secret_key = get_required_secret('JWT_SECRET_KEY', 'JWT secret key') + _admin_token = get_required_secret('ADMIN_TOKEN', 'Admin authentication token') +except ValueError as e: + print(f"⚠️ SECURITY WARNING: {e}") + print("⚠️ Using insecure defaults for development only. Set proper secrets in production!") + _secret_key = os.getenv('SECRET_KEY', os.urandom(32).hex()) + _jwt_secret_key = os.getenv('JWT_SECRET_KEY', os.urandom(32).hex()) + _admin_token = os.getenv('ADMIN_TOKEN', os.urandom(16).hex()) + app.config.update( - SECRET_KEY=os.getenv('SECRET_KEY', 'your-super-secret-key-change-this-in-production-openlearnx-2024'), + SECRET_KEY=_secret_key, MONGODB_URI=os.getenv('MONGODB_URI', 'mongodb://localhost:27017/'), WEB3_PROVIDER_URL=os.getenv('WEB3_PROVIDER_URL', 'http://127.0.0.1:8545'), CONTRACT_ADDRESS=os.getenv('CONTRACT_ADDRESS', '0x739f0aCef964f87Bc7974D972a811f8417d74B4C'), DEPLOYER_PRIVATE_KEY=os.getenv('DEPLOYER_PRIVATE_KEY'), MINTER_PRIVATE_KEY=os.getenv('MINTER_PRIVATE_KEY'), - ADMIN_TOKEN=os.getenv('ADMIN_TOKEN', 'admin-secret-key'), + ADMIN_TOKEN=_admin_token, # ✅ JWT Configuration from your .env - JWT_SECRET_KEY=os.getenv('JWT_SECRET_KEY', 'openlearnx-jwt-secret-key-change-in-production'), + JWT_SECRET_KEY=_jwt_secret_key, JWT_ACCESS_TOKEN_EXPIRES=timedelta(hours=int(os.getenv('JWT_EXPIRATION_HOURS', 168))), # ✅ IPFS Configuration from your .env IPFS_GATEWAY=os.getenv('IPFS_GATEWAY', 'https://ipfs.infura.io:5001'), diff --git a/backend/routes/admin.py b/backend/routes/admin.py index ca82ad0..6e0d38c 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -31,10 +31,11 @@ def admin_required(f): token = auth_header.split(' ')[1] if len(auth_header.split(' ')) > 1 else None print(f"Extracted token: '{token}'") - # Check environment variable first, then fallback to default + # Check environment variable - no fallback for security expected_token = os.getenv('ADMIN_TOKEN') if not expected_token: - expected_token = 'admin-secret-key' + print("❌ ADMIN_TOKEN environment variable not set") + return jsonify({"error": "Server configuration error: ADMIN_TOKEN not configured"}), 500 print(f"Expected token: '{expected_token}'") print(f"Environment ADMIN_TOKEN: '{os.getenv('ADMIN_TOKEN')}'") diff --git a/backend/routes/auth.py b/backend/routes/auth.py index bd7d18d..64bbc8e 100644 --- a/backend/routes/auth.py +++ b/backend/routes/auth.py @@ -16,8 +16,13 @@ mongo_uri = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/') client = MongoClient(mongo_uri) db = client.openlearnx -# JWT secret -JWT_SECRET = os.getenv('JWT_SECRET', 'your-secret-key-here') +# JWT secret - must be set via environment variable +JWT_SECRET = os.getenv('JWT_SECRET') +if not JWT_SECRET: + import warnings + warnings.warn("JWT_SECRET environment variable not set. Using randomly generated secret.", UserWarning) + import secrets as _secrets + JWT_SECRET = _secrets.token_hex(32) @bp.route('/nonce', methods=['POST', 'OPTIONS']) def get_nonce(): diff --git a/frontend/app/admin/login/page.tsx b/frontend/app/admin/login/page.tsx index 303ea23..73a9ca5 100644 --- a/frontend/app/admin/login/page.tsx +++ b/frontend/app/admin/login/page.tsx @@ -15,9 +15,9 @@ export default function AdminLogin() { // Check if already authenticated const checkExistingAuth = async () => { const token = localStorage.getItem('admin_token') - if (token === 'admin-secret-key') { + if (token) { try { - // Verify token with API + // Verify token with API - no hardcoded secret check const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { headers: { 'Authorization': `Bearer ${token}` } }) diff --git a/frontend/app/admin/page.tsx b/frontend/app/admin/page.tsx index b3e2e9b..005f3be 100644 --- a/frontend/app/admin/page.tsx +++ b/frontend/app/admin/page.tsx @@ -62,6 +62,23 @@ export default function AdminDashboard() { const router = useRouter() // Authentication logic + // Helper function to get admin token from localStorage + const getAdminToken = (): string | null => { + if (typeof window !== 'undefined') { + return localStorage.getItem('admin_token') + } + return null + } + + // Helper function to get authorization headers + const getAuthHeaders = (): Record => { + const token = getAdminToken() + return { + 'Authorization': token ? `Bearer ${token}` : '', + 'Content-Type': 'application/json' + } + } + useEffect(() => { setIsClient(true) @@ -69,31 +86,28 @@ export default function AdminDashboard() { try { await new Promise(resolve => setTimeout(resolve, 500)) - const token = localStorage.getItem('admin_token') + const token = getAdminToken() if (!token) { router.push('/admin/login') return } - if (token === 'admin-secret-key') { - try { - const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { - headers: { 'Authorization': `Bearer ${token}` } - }) - - if (response.ok) { - setIsAuthenticated(true) - fetchData() - } else { - localStorage.removeItem('admin_token') - router.push('/admin/login') - } - } catch (apiError) { + // Verify token with API - no hardcoded secret check + try { + const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { + headers: { 'Authorization': `Bearer ${token}` } + }) + + if (response.ok) { setIsAuthenticated(true) fetchData() + } else { + localStorage.removeItem('admin_token') + router.push('/admin/login') } - } else { + } catch (apiError) { + // If API is unavailable, don't allow access without verification localStorage.removeItem('admin_token') router.push('/admin/login') } @@ -114,10 +128,7 @@ export default function AdminDashboard() { const fetchCourses = async () => { try { const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { - headers: { - 'Authorization': 'Bearer admin-secret-key', - 'Content-Type': 'application/json' - } + headers: getAuthHeaders() }) if (!response.ok) { @@ -141,7 +152,7 @@ export default function AdminDashboard() { const fetchStats = async () => { try { const response = await fetch('http://127.0.0.1:5000/api/admin/dashboard', { - headers: { 'Authorization': 'Bearer admin-secret-key' } + headers: getAuthHeaders() }) if (response.ok) { const data = await response.json() @@ -161,10 +172,7 @@ export default function AdminDashboard() { console.log('🔍 Fetching modules for course:', courseId) // Debug log const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}/modules`, { - headers: { - 'Authorization': 'Bearer admin-secret-key', - 'Content-Type': 'application/json' - } + headers: getAuthHeaders() }) console.log('🔍 Modules response status:', response.status) // Debug log @@ -209,10 +217,7 @@ export default function AdminDashboard() { console.log('🔍 Fetching lessons for module:', moduleId) // Debug log const response = await fetch(`http://127.0.0.1:5000/api/admin/modules/${moduleId}/lessons`, { - headers: { - 'Authorization': 'Bearer admin-secret-key', - 'Content-Type': 'application/json' - } + headers: getAuthHeaders() }) console.log('🔍 Lessons response status:', response.status) // Debug log @@ -253,10 +258,7 @@ export default function AdminDashboard() { try { const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer admin-secret-key' - }, + headers: getAuthHeaders(), body: JSON.stringify(formData) }) @@ -277,10 +279,7 @@ export default function AdminDashboard() { try { const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}`, { method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer admin-secret-key' - }, + headers: getAuthHeaders(), body: JSON.stringify(formData) }) @@ -301,7 +300,7 @@ export default function AdminDashboard() { try { const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}`, { method: 'DELETE', - headers: { 'Authorization': 'Bearer admin-secret-key' } + headers: getAuthHeaders() }) if (response.ok) { @@ -333,10 +332,7 @@ export default function AdminDashboard() { const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${selectedCourse?.id}/modules`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer admin-secret-key' - }, + headers: getAuthHeaders(), body: JSON.stringify(formData) }) @@ -358,10 +354,7 @@ export default function AdminDashboard() { const response = await fetch(`http://127.0.0.1:5000/api/admin/modules/${moduleId}/lessons`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer admin-secret-key' - }, + headers: getAuthHeaders(), body: JSON.stringify(formData) }) diff --git a/frontend/app/courses/[courseId]/page.tsx b/frontend/app/courses/[courseId]/page.tsx index 30c1898..3de18ad 100644 --- a/frontend/app/courses/[courseId]/page.tsx +++ b/frontend/app/courses/[courseId]/page.tsx @@ -108,37 +108,20 @@ export default function CoursePage() { let modulesData = null let modulesResponse = null + // Use public endpoint for course page (not admin endpoint) try { - modulesResponse = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}/modules`, { + modulesResponse = await fetch(`http://127.0.0.1:5000/api/courses/${courseId}/modules`, { headers: { - 'Authorization': 'Bearer admin-secret-key', 'Content-Type': 'application/json' } }) if (modulesResponse.ok) { modulesData = await modulesResponse.json() - console.log('✅ Modules loaded from admin endpoint:', modulesData) - } - } catch (adminError) { - console.log('⚠️ Admin endpoint failed, trying public endpoint') - } - - if (!modulesData || !modulesResponse?.ok) { - try { - modulesResponse = await fetch(`http://127.0.0.1:5000/api/courses/${courseId}/modules`, { - headers: { - 'Content-Type': 'application/json' - } - }) - - if (modulesResponse.ok) { - modulesData = await modulesResponse.json() - console.log('✅ Modules loaded from public endpoint:', modulesData) - } - } catch (publicError) { - console.error('❌ Both module endpoints failed') + console.log('✅ Modules loaded from public endpoint:', modulesData) } + } catch (publicError) { + console.error('❌ Module endpoint failed') } if (modulesData) { @@ -185,21 +168,13 @@ export default function CoursePage() { try { console.log('🔍 Fetching lessons for module:', module.id) - let lessonsResponse = await fetch(`http://127.0.0.1:5000/api/admin/modules/${module.id}/lessons`, { + // Use public endpoint for course page (not admin endpoint) + const lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, { headers: { - 'Authorization': 'Bearer admin-secret-key', 'Content-Type': 'application/json' } }) - if (!lessonsResponse.ok) { - lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, { - headers: { - 'Content-Type': 'application/json' - } - }) - } - if (lessonsResponse.ok) { const lessonData = await lessonsResponse.json() console.log(`✅ Lessons loaded for module ${module.id}:`, lessonData)