Skip to content

Commit 10aa214

Browse files
xusheng6claude
andcommitted
Integrate reviewer tool as Flask Blueprint (#23)
Integrates the reviewer tool from crackmesone_reviewer_tool repository as a Flask Blueprint at /review, sharing the same MongoDB connection and session management with the main site. Key changes: - Added review/ folder with Flask Blueprint for content moderation - Reviewer authentication uses Flask sessions (separate from main site) - Integrated delete.py and validate.py scripts directly into routes - Renamed endpoints for clarity (delc→rejectcrackme, vals→approvesolution) - Fixed security vulnerabilities: - Changed /deleteuser from @token_required to @admin_required - Fixed NoSQL regex injection with re.escape() in 6 locations - Fixed argument injection in zip commands with -- separator - All state-changing actions now require POST with CSRF tokens - Added Discord notifications for approved crackmes/solutions - Added comprehensive logging for all reviewer operations - Updated README with reviewer tool configuration documentation Access control: - Reviewers: approve/reject pending submissions - Admins: all reviewer actions + delete approved content, manage users Configuration: - config.json: Reviewer.Enabled, Reviewer.PasswordSalt - review/users.json: Reviewer credentials (username, password_hash, is_admin) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7e55b5f commit 10aa214

27 files changed

Lines changed: 3342 additions & 219 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ static/solution/*
4848
config/config.json
4949
users.json
5050
.env
51+
review/review_operations.log
52+
review/users.json
5153

5254
# Testing
5355
.pytest_cache/

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ crackmesone_python/
124124
│ ├── crackmesone.service # Systemd service file
125125
│ ├── setup.sh # First-time setup script
126126
│ └── deploy.sh # Deployment script
127+
├── review/ # Reviewer tool (moderation interface)
128+
│ ├── routes.py # Reviewer Flask blueprint
129+
│ ├── users.json # Reviewer credentials
130+
│ └── templates/ # Reviewer templates
131+
├── script/ # Utility scripts
132+
│ └── generate_reviewer_password_hash.py # Password hash generator
127133
├── templates/ # Jinja2 templates
128134
├── static/ # Static files (CSS, JS, images)
129135
├── tmp/ # Upload staging area
@@ -142,6 +148,7 @@ crackmesone_python/
142148
- Search functionality
143149
- RSS feed
144150
- Notifications
151+
- Content moderation (reviewer tool for approving/rejecting submissions)
145152

146153
## Configuration
147154

@@ -158,6 +165,67 @@ Edit `config/config.json`:
158165
- **Recaptcha.Secret**: Your reCAPTCHA secret key
159166
- **Discord.Enabled**: Enable/disable Discord notifications for new submissions
160167
- **Discord.WebhookURL**: Your Discord webhook URL (get from Discord channel settings → Integrations → Webhooks)
168+
- **Reviewer.Enabled**: Enable/disable the reviewer tool (for moderating submissions)
169+
- **Reviewer.PasswordSalt**: Salt used for hashing reviewer passwords (change in production!)
170+
171+
### Reviewer Tool
172+
173+
The reviewer tool is a separate authentication system for site moderators to approve/reject crackme and solution submissions. It is accessed at `/review`.
174+
175+
#### Enabling the Reviewer Tool
176+
177+
1. Set `Reviewer.Enabled` to `true` in `config/config.json`
178+
2. Set a secure random string for `Reviewer.PasswordSalt`
179+
180+
#### Reviewer Credentials (`review/users.json`)
181+
182+
Reviewer accounts are stored in `review/users.json` with the following format:
183+
184+
```json
185+
{
186+
"username": {
187+
"password_hash": "sha256-hash-of-password-plus-salt",
188+
"is_admin": false
189+
}
190+
}
191+
```
192+
193+
- **password_hash**: SHA256 hash of the password concatenated with the `PasswordSalt` from config
194+
- **is_admin**: If `true`, the user has admin privileges (can delete approved content, manage reviewers, delete users)
195+
196+
#### Creating Reviewer Accounts
197+
198+
Use the password hash generator script to create password hashes:
199+
200+
```bash
201+
python script/generate_reviewer_password_hash.py <password>
202+
```
203+
204+
Then add the username and hash to `review/users.json`:
205+
206+
```json
207+
{
208+
"newreviewer": {
209+
"password_hash": "<output-from-script>",
210+
"is_admin": false
211+
}
212+
}
213+
```
214+
215+
Alternatively, an existing admin can add new reviewers through the web interface at `/review/managereviewers`.
216+
217+
#### Reviewer vs Admin Permissions
218+
219+
| Action | Reviewer | Admin |
220+
|--------|----------|-------|
221+
| Approve/reject pending crackmes | Yes | Yes |
222+
| Approve/reject pending solutions | Yes | Yes |
223+
| Delete approved crackmes | No | Yes |
224+
| Delete approved solutions | No | Yes |
225+
| Delete comments | No | Yes |
226+
| Delete user accounts | No | Yes |
227+
| Reset user passwords | No | Yes |
228+
| Manage reviewer accounts | No | Yes |
161229

162230
## Previous Codebase
163231

app/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ def create_app(config_path=None):
5252
from app.controllers import register_blueprints
5353
register_blueprints(app)
5454

55+
# Register reviewer blueprint (separate authentication system)
56+
reviewer_config = config.get('Reviewer', {})
57+
if reviewer_config.get('Enabled', False):
58+
from review.routes import reviewer_bp, init_reviewer
59+
from review.logger import init_logger as init_reviewer_logger
60+
app.config['REVIEWER_PASSWORD_SALT'] = reviewer_config.get('PasswordSalt', 'default_salt')
61+
init_reviewer(app)
62+
# Use private webhook for reviewer operation logs
63+
discord_config = config.get('Discord', {})
64+
private_webhook = discord_config.get('WebhookPrivate', '') if discord_config.get('Enabled', False) else None
65+
init_reviewer_logger(discord_webhook=private_webhook)
66+
app.register_blueprint(reviewer_bp)
67+
# Exempt reviewer routes from main CSRF (reviewer has its own CSRF)
68+
csrf.exempt(reviewer_bp)
69+
5570
# Register template context processors
5671
@app.context_processor
5772
def inject_globals():

app/controllers/login.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
login_bp = Blueprint('login', __name__)
1313

1414

15+
def clear_main_auth():
16+
"""Clear main site auth session keys only."""
17+
session.pop('name', None)
18+
session.pop('email', None)
19+
session.pop('login_attempt', None)
20+
21+
1522
@login_bp.route('/login', methods=['GET'])
1623
@anonymous_required
1724
def login_get():
@@ -46,7 +53,7 @@ def login_post():
4653
# Check password
4754
if match_string(user['password'], password):
4855
# Login successful
49-
session.clear()
56+
clear_main_auth()
5057
session['email'] = user['email']
5158
session['name'] = user['name']
5259
flash('Login successful!', FLASH_SUCCESS)
@@ -72,7 +79,7 @@ def login_post():
7279
def logout():
7380
"""Log out the user."""
7481
if session.get('name'):
75-
session.clear()
82+
clear_main_auth()
7683
flash('Goodbye!', 'alert-info')
7784

7885
return redirect('/')

app/services/discord.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""
22
Discord webhook notification service.
3+
4+
Two webhooks are supported:
5+
- WebhookPublic: Public channel for approved crackmes/solutions notifications
6+
- WebhookPrivate: Private/admin channel for pending submissions and reviewer logs
37
"""
48

59
import requests
@@ -20,24 +24,26 @@ def is_enabled():
2024
return discord_config.get('Enabled', False)
2125

2226

23-
def get_webhook_url():
24-
"""Get the Discord webhook URL."""
25-
return discord_config.get('WebhookURL', '')
27+
def get_public_webhook():
28+
"""Get the public Discord webhook URL (for approved items)."""
29+
return discord_config.get('WebhookPublic', '')
30+
2631

32+
def get_private_webhook():
33+
"""Get the private Discord webhook URL (for pending items and logs)."""
34+
return discord_config.get('WebhookPrivate', '')
2735

28-
def send_notification(message: str) -> bool:
29-
"""Send a notification to Discord via webhook.
36+
37+
def send_to_webhook(webhook_url: str, message: str) -> bool:
38+
"""Send a message to a specific Discord webhook.
3039
3140
Args:
41+
webhook_url: The webhook URL to send to
3242
message: The message to send
3343
3444
Returns:
3545
True if the notification was sent successfully, False otherwise
3646
"""
37-
if not is_enabled():
38-
return True
39-
40-
webhook_url = get_webhook_url()
4147
if not webhook_url:
4248
return False
4349

@@ -52,8 +58,38 @@ def send_notification(message: str) -> bool:
5258
return False
5359

5460

61+
def send_public_notification(message: str) -> bool:
62+
"""Send a notification to the public Discord channel.
63+
64+
Args:
65+
message: The message to send
66+
67+
Returns:
68+
True if the notification was sent successfully, False otherwise
69+
"""
70+
if not is_enabled():
71+
return True
72+
return send_to_webhook(get_public_webhook(), message)
73+
74+
75+
def send_private_notification(message: str) -> bool:
76+
"""Send a notification to the private/admin Discord channel.
77+
78+
Args:
79+
message: The message to send
80+
81+
Returns:
82+
True if the notification was sent successfully, False otherwise
83+
"""
84+
if not is_enabled():
85+
return True
86+
return send_to_webhook(get_private_webhook(), message)
87+
88+
5589
def notify_new_crackme(username: str, crackme_name: str) -> bool:
56-
"""Send notification for a new crackme submission.
90+
"""Send notification for a new crackme submission (pending review).
91+
92+
Sent to PRIVATE channel - only admins/reviewers need to see this.
5793
5894
Args:
5995
username: The user who submitted the crackme
@@ -63,11 +99,13 @@ def notify_new_crackme(username: str, crackme_name: str) -> bool:
6399
True if notification was sent successfully
64100
"""
65101
message = f"New crackme submission awaiting review: **{crackme_name}** by **{username}**"
66-
return send_notification(message)
102+
return send_private_notification(message)
67103

68104

69105
def notify_new_solution(username: str, crackme_name: str) -> bool:
70-
"""Send notification for a new solution submission.
106+
"""Send notification for a new solution submission (pending review).
107+
108+
Sent to PRIVATE channel - only admins/reviewers need to see this.
71109
72110
Args:
73111
username: The user who submitted the solution
@@ -77,4 +115,4 @@ def notify_new_solution(username: str, crackme_name: str) -> bool:
77115
True if notification was sent successfully
78116
"""
79117
message = f"New solution submission awaiting review: Solution for **{crackme_name}** by **{username}**"
80-
return send_notification(message)
118+
return send_private_notification(message)

app/services/session.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ def get_session():
1818

1919

2020
def clear_session():
21-
"""Clear all session values."""
22-
flask_session.clear()
21+
"""Clear main site auth session keys only."""
22+
flask_session.pop('name', None)
23+
flask_session.pop('email', None)
24+
flask_session.pop('login_attempt', None)
2325

2426

2527
def get_username():

config/config.json.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
},
1919
"Discord": {
2020
"Enabled": false,
21-
"WebhookURL": "your-discord-webhook-url"
21+
"WebhookPublic": "your-public-discord-webhook-url",
22+
"WebhookPrivate": "your-private-discord-webhook-url"
23+
},
24+
"Reviewer": {
25+
"Enabled": false,
26+
"PasswordSalt": "change-this-to-a-secure-random-salt"
2227
}
2328
}

review/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""
2+
Reviewer Tool - Integrated as Flask Blueprint.
3+
4+
This module provides the reviewer functionality for managing crackme submissions.
5+
"""

0 commit comments

Comments
 (0)