Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions CRITICAL_ISSUES_REPORT.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Critical Issues Report - WikiContest

**Generated:** $(date)
**Status:** ⚠️ CRITICAL - Immediate Action Required
**Status:** CRITICAL - Immediate Action Required

## Executive Summary

Expand Down Expand Up @@ -159,7 +159,7 @@ Debug mode is hardcoded to `True` in the application startup code. This should b
```python
# backend/app/__init__.py:978
app.run(
debug=True, # ⚠️ Hardcoded!
debug=True, # Hardcoded!
host='0.0.0.0',
port=5000
)
Expand Down Expand Up @@ -202,7 +202,7 @@ Default database connection string includes weak default password `'password'`.
```python
SQLALCHEMY_DATABASE_URI = os.getenv(
'DATABASE_URL',
'mysql+pymysql://root:password@localhost/wikicontest' # ⚠️ Weak default
'mysql+pymysql://root:password@localhost/wikicontest' # Weak default
)
```

Expand Down Expand Up @@ -244,7 +244,7 @@ The `update_contest` route has `@require_auth` but is missing `@handle_errors` d
```python
@contest_bp.route("/<int:contest_id>", methods=["PUT"])
@require_auth
# ⚠️ Missing @handle_errors
# Missing @handle_errors
def update_contest(contest_id):
```

Expand All @@ -270,15 +270,15 @@ def update_contest(contest_id):

### Immediate (Before Any Production Deployment)

1. **Fix hardcoded secrets** - Remove `'rohank10'` defaults
2. **Secure debug endpoint** - Add authentication or remove
3. **Revoke OAuth credentials** - Generate new ones, move to env vars
4. **Disable debug mode** - Use environment variable
1. **Fix hardcoded secrets** - Remove `'rohank10'` defaults
2. **Secure debug endpoint** - Add authentication or remove
3. **Revoke OAuth credentials** - Generate new ones, move to env vars
4. **Disable debug mode** - Use environment variable

### High Priority (Before Next Release)

5. **Fix database password default** - Require DATABASE_URL
6. **Add error handler** - Fix update_contest route
5. **Fix database password default** - Require DATABASE_URL
6. **Add error handler** - Fix update_contest route

### Additional Recommendations

Expand Down
20 changes: 10 additions & 10 deletions backend/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ def create_app():
import secrets
if not secret_key:
secret_key = secrets.token_urlsafe(48)
print("⚠️ WARNING: SECRET_KEY not set in environment. Generated temporary key.")
print(" WARNING: SECRET_KEY not set in environment. Generated temporary key.")
print(" Set SECRET_KEY in environment for production!")
if not jwt_secret_key:
jwt_secret_key = secrets.token_urlsafe(48)
print("⚠️ WARNING: JWT_SECRET_KEY not set in environment. Generated temporary key.")
print(" WARNING: JWT_SECRET_KEY not set in environment. Generated temporary key.")
print(" Set JWT_SECRET_KEY in environment for production!")
flask_app.config['SECRET_KEY'] = secret_key
flask_app.config['JWT_SECRET_KEY'] = jwt_secret_key
Expand Down Expand Up @@ -286,9 +286,9 @@ def check_cookie():
print(f'🔐 [COOKIE CHECK] {log_msg}')
# Special check: If username is Adityakumar0545, verify role is superadmin
if db_username == 'Adityakumar0545':
print(f'⚠️ [SPECIAL CHECK] User Adityakumar0545 - Role from DB: {db_role}')
print(f' [SPECIAL CHECK] User Adityakumar0545 - Role from DB: {db_role}')
if db_role != 'superadmin':
print(f' [ERROR] Expected superadmin but got: {db_role}')
print(f' [ERROR] Expected superadmin but got: {db_role}')
else:
print(' [SUCCESS] Role is correct: superadmin')
except Exception as error: # pylint: disable=broad-exception-caught
Expand Down Expand Up @@ -352,9 +352,9 @@ def check_cookie():
print(f'🔐 [FINAL RESPONSE] {log_msg}')
# Special check for Adityakumar0545
if response_data.get("username") == 'Adityakumar0545':
print(f'⚠️ [SPECIAL CHECK] Adityakumar0545 - Role in response: {response_data.get("role")}')
print(f' [SPECIAL CHECK] Adityakumar0545 - Role in response: {response_data.get("role")}')
if response_data.get("role") != 'superadmin':
print(f' [ERROR] Role should be superadmin but is: {response_data.get("role")}')
print(f' [ERROR] Role should be superadmin but is: {response_data.get("role")}')
else:
print(' [SUCCESS] Role is correctly set to superadmin in response')
except Exception as error: # pylint: disable=broad-exception-caught
Expand Down Expand Up @@ -415,7 +415,7 @@ def debug_user_role(username):
).fetchone()

if not result:
print(f' [DEBUG] User not found: {username}')
print(f' [DEBUG] User not found: {username}')
return jsonify({
'error': 'User not found',
'username': username
Expand Down Expand Up @@ -454,7 +454,7 @@ def debug_user_role(username):
# Special check for Adityakumar0545
if username == 'Adityakumar0545':
if user_data['role'] != 'superadmin':
print(f' [ERROR] Adityakumar0545 should have superadmin but has: {user_data["role"]}')
print(f' [ERROR] Adityakumar0545 should have superadmin but has: {user_data["role"]}')
else:
print(' [SUCCESS] Adityakumar0545 has correct superadmin role')

Expand All @@ -464,7 +464,7 @@ def debug_user_role(username):
# Catch all exceptions to prevent application crash
error_msg = f'Debug user role error: {str(error)}'
current_app.logger.error(error_msg)
print(f' [ERROR] {error_msg}')
print(f' [ERROR] {error_msg}')
return jsonify({
'error': 'Failed to query user',
'details': str(error)
Expand Down Expand Up @@ -1016,7 +1016,7 @@ def internal_error(_error):
# Default to False for production safety
debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() == 'true'
if debug_mode:
print("⚠️ WARNING: Debug mode is enabled. Disable in production!")
print(" WARNING: Debug mode is enabled. Disable in production!")
app.run(
debug=debug_mode, # Controlled by FLASK_DEBUG environment variable
host='0.0.0.0', # Allow connections from any IP
Expand Down
2 changes: 1 addition & 1 deletion backend/app/models/contest_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def to_dict(self):
'error_message': self.error_message if self.is_failed() else None,
'report_metadata': self.get_metadata(),
'generated_by': self.generated_by,
# These come from BaseModel - should work now
# These come from BaseModel - should work now
'created_at': self.created_at.isoformat() if hasattr(self, 'created_at') and self.created_at else None,
'updated_at': self.updated_at.isoformat() if hasattr(self, 'updated_at') and self.updated_at else None,
}
Expand Down
42 changes: 13 additions & 29 deletions backend/app/routes/report_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def generate_report(contest_id):
submission_count = Submission.query.filter_by(contest_id=contest_id).count()

if submission_count == 0:
print(f"⚠️ Warning: No submissions to report on")
print(f" Warning: No submissions to report on")
# Continue anyway - empty report is valid

# Step 6: Create report record
Expand All @@ -75,7 +75,6 @@ def generate_report(contest_id):
)
db.session.add(report)
db.session.commit()
print(f" Report ID: {report.id}")
except Exception as db_error:
print(f"Database error creating report: {db_error}")
traceback.print_exc()
Expand All @@ -93,14 +92,10 @@ def generate_report(contest_id):

try:
if report_type == 'csv':
print(f" Creating CSV builder...")
builder = CSVReportBuilder(contest, report_metadata)
print(f" Generating CSV...")
file_path = builder.generate()
else: # pdf
print(f" Creating PDF builder...")
builder = PDFReportBuilder(contest, report_metadata)
print(f" Generating PDF...")
file_path = builder.generate()

# Verify file exists
Expand Down Expand Up @@ -145,7 +140,7 @@ def generate_report(contest_id):
report.error_message = str(e)
db.session.commit()
except Exception as db_error:
print(f"⚠️ Failed to update report status: {db_error}")
print(f"Failed to update report status: {db_error}")
db.session.rollback()

return jsonify({
Expand Down Expand Up @@ -207,7 +202,6 @@ def download_report(report_id):

# Check if report is ready
if report.status != 'completed':
print(f"⚠️ Report not ready: {report.status}")
return jsonify({
'error': 'Report not ready',
'status': report.status,
Expand All @@ -216,16 +210,13 @@ def download_report(report_id):

# Check if file exists
if not report.file_path:
print(f"❌ No file path in database")
return jsonify({'error': 'Report file path missing'}), 404

if not os.path.exists(report.file_path):
print(f"❌ File not found: {report.file_path}")
return jsonify({'error': 'Report file not found on disk'}), 404

# Send file
filename = f"contest_{report.contest_id}_report.{report.report_type}"
print(f"✅ Sending file: {filename} ({os.path.getsize(report.file_path)} bytes)")

return send_file(
report.file_path,
Expand All @@ -235,7 +226,7 @@ def download_report(report_id):
)

except Exception as e:
print(f" Download error: {e}")
print(f" Download error: {e}")
traceback.print_exc()
return jsonify({'error': f'Failed to download report: {str(e)}'}), 500

Expand All @@ -262,7 +253,7 @@ def report_status(report_id):
}), 200

except Exception as e:
print(f" Status check error: {e}")
print(f" Status check error: {e}")
return jsonify({'error': str(e)}), 500


Expand All @@ -272,7 +263,6 @@ def report_status(report_id):
def preview_report(contest_id):
"""Preview report data without generating full file"""
try:
print(f"\n👁️ Preview request for contest {contest_id}")

current_user = request.current_user
contest = Contest.query.get_or_404(contest_id)
Expand All @@ -289,9 +279,8 @@ def preview_report(contest_id):
# Fetch preview data with error handling
try:
stats = get_submission_statistics(contest_id)
print(f" ✅ Statistics loaded")
except Exception as e:
print(f" Statistics failed: {e}")
print(f" Statistics failed: {e}")
traceback.print_exc()
return jsonify({
'error': 'Failed to fetch statistics',
Expand All @@ -300,9 +289,8 @@ def preview_report(contest_id):

try:
top_contributors = get_top_contributors(contest_id, limit=top_n)
print(f" ✅ Top contributors loaded: {len(top_contributors)}")
except Exception as e:
print(f" Contributors failed: {e}")
print(f" Contributors failed: {e}")
traceback.print_exc()
top_contributors = []

Expand All @@ -321,7 +309,7 @@ def preview_report(contest_id):
}), 200

except Exception as e:
print(f" Preview error: {e}")
print(f" Preview error: {e}")
traceback.print_exc()
return jsonify({'error': str(e)}), 500

Expand All @@ -332,7 +320,6 @@ def preview_report(contest_id):
def delete_report(report_id):
"""Delete a generated report"""
try:
print(f"\n🗑️ Delete request for report {report_id}")

current_user = request.current_user
report = ContestReport.query.get_or_404(report_id)
Expand All @@ -346,23 +333,20 @@ def delete_report(report_id):
if report.file_path and os.path.exists(report.file_path):
try:
os.remove(report.file_path)
print(f"✅ Deleted file: {report.file_path}")
except Exception as e:
print(f"⚠️ File delete error: {e}")
print(f" File delete error: {e}")

# Delete database record
db.session.delete(report)
db.session.commit()

print(f"✅ Report {report_id} deleted successfully")

return jsonify({
'success': True,
'message': 'Report deleted successfully'
}), 200

except Exception as e:
print(f" Delete error: {e}")
print(f" Delete error: {e}")
traceback.print_exc()
return jsonify({'error': str(e)}), 500

Expand All @@ -382,21 +366,21 @@ def report_health():
db.session.execute('SELECT 1')
dependencies['database'] = True
except Exception as e:
print(f" Database check failed: {e}")
print(f" Database check failed: {e}")

# Check reportlab
try:
import reportlab
dependencies['reportlab'] = True
except ImportError:
print(" reportlab not installed")
print(" reportlab not installed")

# Check matplotlib
try:
import matplotlib
dependencies['matplotlib'] = True
except ImportError:
print(" matplotlib not installed")
print(" matplotlib not installed")

# Check reports directory
try:
Expand All @@ -412,7 +396,7 @@ def report_health():
os.remove(test_file)
dependencies['reports_directory'] = True
except Exception as e:
print(f" Reports directory check failed: {e}")
print(f" Reports directory check failed: {e}")

all_ok = all(dependencies.values())

Expand Down
2 changes: 1 addition & 1 deletion backend/app/utils/report_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ def _save_file(self, content, extension):
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)

print(f" Comprehensive Report saved: {file_path}")
print(f" Comprehensive Report saved: {file_path}")

return file_path

Expand Down
8 changes: 4 additions & 4 deletions docs/CREATE_LOCAL_OAUTH_CONSUMER.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ WikiContest Local Development
```
OAuth 1.0a
```
⚠️ **Must match:** Your code uses OAuth 1.0a protocol.
**Must match:** Your code uses OAuth 1.0a protocol.

#### Application Description
```
Expand All @@ -60,7 +60,7 @@ Local development instance of WikiContest for testing and development purposes
http://localhost:5000/api/user/oauth/callback
```

⚠️ **CRITICAL - Must be EXACTLY:**
**CRITICAL - Must be EXACTLY:**
- **Protocol:** `http://` (NOT `https://`)
- **Host:** `localhost` (NOT `127.0.0.1` or any domain)
- **Port:** `5000` (must match your Flask server port)
Expand All @@ -75,7 +75,7 @@ http://localhost:5000/api/user/oauth/callback
```
☐ No (Leave unchecked)
```
⚠️ **IMPORTANT:** Must be set to "No" to allow other users to test your application!
**IMPORTANT:** Must be set to "No" to allow other users to test your application!

#### Applicable Grants / Permissions
```
Expand All @@ -92,7 +92,7 @@ After clicking **"Propose consumer"**, you'll receive:
- **Consumer Key:** (e.g., `3f383c834a07a181723f1a1de566f7cf`)
- **Consumer Secret:** (e.g., `62c40e0fde2377613d1f82b9b7aabc9fe2a73b30`)

⚠️ **CRITICAL:** Copy BOTH credentials immediately! The consumer secret is only displayed once. If you lose it, you'll need to create a new consumer.
**CRITICAL:** Copy BOTH credentials immediately! The consumer secret is only displayed once. If you lose it, you'll need to create a new consumer.

**Save them to a secure location temporarily.**

Expand Down
Loading