Skip to content

feat: 增加管理员后台系统与 IP 批量管理功能#1

Merged
dunea merged 2 commits into
mainfrom
feature/admin-panel
Jun 22, 2026
Merged

feat: 增加管理员后台系统与 IP 批量管理功能#1
dunea merged 2 commits into
mainfrom
feature/admin-panel

Conversation

@dunea

@dunea dunea commented Jun 22, 2026

Copy link
Copy Markdown
Owner

已完成管理员后台、角色选择页面、用户管理以及代理 IP 批量导入/软删除功能开发与联调。

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a full admin web console under /web/admin with dashboard, user management, and proxy management (including add/import, enable/disable, and batch delete).
    • Introduced an admin role-selection page to route admins to the correct console.
    • Added an admin-only link in the main authenticated navigation.
  • Improvements
    • Admin status is now reflected in the authentication flow for cookie-based access control.
    • Automatic proxy assignment is now used for phone/session logins (proxy picker removed).
  • Bug Fixes
    • Login/assignment failures now show clearer, targeted error panels.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an admin backend to the web app. JWT tokens gain an is_admin claim; post-login redirect routes admins to a role-selection page. A new get_current_admin_from_cookie dependency enforces admin access. A new admin router provides user and proxy management endpoints. Account login flows auto-assign proxies instead of user-selected ones. Full admin HTML templates are introduced.

Changes

Admin Backend Feature

Layer / File(s) Summary
JWT is_admin claim and SimpleUser propagation
app/service/auth_service.py, app/web/__init__.py
Both _build_access_token and _build_refresh_token embed an is_admin boolean claim; SimpleUser in the web init is expanded to read and store is_admin from the decoded JWT for template use.
Admin auth dependency
app/web/dependencies.py
New get_current_admin_from_cookie dependency decodes the JWT cookie, loads the User, and enforces admin status and account active state with conditional redirects to /web/login or /web/dashboard.
Post-login redirect based on admin status
app/web/routes/auth.py
Login POST handler decodes the is_admin claim from the JWT and redirects admins to /web/select-role instead of /web/dashboard; both login endpoints set samesite="lax" on the web_token cookie.
Admin router setup and role selection
app/web/routes/admin.py (lines 1–38)
Creates the admin APIRouter under /web prefix with a role-selection endpoint that loads the current user from auth cookie, redirects inactive users to login and non-admins to dashboard, and renders the role picker page for admins.
Admin dashboard metrics and overview
app/web/routes/admin.py (lines 41–79)
Dashboard endpoint performs multiple global count queries (users, telegram accounts, proxies, scheduled tasks) and computes today's messages based on UTC day start, then renders the dashboard template with all metrics.
Admin user management endpoints
app/web/routes/admin.py (lines 81–189)
List endpoint, toggle-active (with self-modification guard), toggle-admin (prevents current admin from changing own role), and reset-api-key (generates new UUID hex key)—all return HTMX-compatible HTML fragments for dynamic updates.
Admin proxy management endpoints
app/web/routes/admin.py (lines 191–441)
List (with outer-join active account counts), add-single, _parse_proxy_line helper (URL/IPv6/colon formats), bulk-import, toggle-active, soft-delete, and batch-soft-delete—all with HTMX-friendly responses.
Proxy auto-assignment in account login flows
app/web/routes/accounts.py, templates/accounts/login_flow.html
New _auto_assign_proxy helper selects the active proxy with the fewest assigned accounts; phone login and session-string login handlers call it unconditionally and return error panels on failure; user-facing proxy selectors are replaced with hidden empty inputs.
Admin router registration
app/web/__init__.py
register_web_routes imports and includes the new admin_router under the /web prefix.
Admin base layout template
templates/admin/admin_base.html
Shared template skeleton: navigation bar with active-link styling, admin email display, flash success/error banners, mobile menu with toggle function, and content block for child templates.
Workspace/role selection page
templates/admin/select_role.html
Renders workspace picker with personalized greeting, two navigation cards (frontend and admin backends), and logout button.
Admin dashboard template
templates/admin/dashboard.html
Metric cards for users, telegram accounts, proxies, and tasks (with active/online subsets), today's message count, and quick-operation links.
Admin user management table template
templates/admin/users.html
User table with ID, email, admin status, active status, and API key columns; HTMX buttons for state toggles on non-current users; masked API key with click-to-reveal and reset button.
Admin proxy management template
templates/admin/proxies.html
Three sections: single-proxy form, bulk-import form with format documentation, and proxy list with per-proxy active count, password masking, HTMX toggles/delete, and batch-delete with master checkbox and JS helpers.
Admin link in main navigation
templates/base.html
Conditional admin dashboard link added to both desktop and mobile authenticated menus for current_user.is_admin; mobile menu toggle button receives id and toggle function now sets aria-expanded.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant LoginHandler
  participant AuthService
  participant AdminDependency
  participant AdminRouter
  participant DB

  Browser->>LoginHandler: POST /web/login (credentials)
  LoginHandler->>AuthService: LoginUser
  AuthService-->>LoginHandler: access_token (with is_admin claim)
  LoginHandler->>LoginHandler: decode JWT, read is_admin
  alt is_admin == true
    LoginHandler-->>Browser: redirect /web/select-role (set web_token cookie)
  else
    LoginHandler-->>Browser: redirect /web/dashboard (set web_token cookie)
  end

  Browser->>AdminRouter: GET /web/admin/dashboard
  AdminRouter->>AdminDependency: get_current_admin_from_cookie(web_token)
  AdminDependency->>DB: load User by sub
  alt token missing/invalid or user inactive
    AdminDependency-->>Browser: redirect /web/login
  else user not admin
    AdminDependency-->>Browser: redirect /web/dashboard
  else admin ok
    AdminDependency-->>AdminRouter: User
    AdminRouter->>DB: aggregate count queries
    DB-->>AdminRouter: stats
    AdminRouter-->>Browser: render dashboard.html
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 A rabbit built a back door, tucked neat and clean,
With JWT claims of is_admin supreme.
Admins select their role with a click,
Proxies auto-assigned — no more pick.
Users managed in tables, soft-delete with care,
The warren's well-governed, both front and back there! 🏠

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding an admin backend system and IP bulk management features, which are the primary objectives of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/admin-panel

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
app/web/routes/accounts.py (1)

33-46: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Avoid full in-memory proxy/account scans in the login hot path.

_auto_assign_proxy currently loads all active proxies and all active accounts, then counts/sorts in Python. This will become expensive as account volume grows; move counting/min selection into SQL.

Refactor sketch
+from sqlalchemy import and_, func, select
+
 async def _auto_assign_proxy(db_session: AsyncSession) -> int:
-    proxies = (await db_session.scalars(select(ProxyInfo).where(ProxyInfo.is_active == True))).all()
-    if not proxies:
-        raise ValueError("系统暂无可用代理 IP,请联系管理员配置。")
-    
-    accounts = (await db_session.scalars(select(TelegramAccount).where(TelegramAccount.is_active == True))).all()
-    proxy_counts = {p.id: 0 for p in proxies}
-    for acc in accounts:
-        if acc.proxy_id in proxy_counts:
-            proxy_counts[acc.proxy_id] += 1
-            
-    sorted_proxies = sorted(proxies, key=lambda p: proxy_counts[p.id])
-    return sorted_proxies[0].id
+    stmt = (
+        select(ProxyInfo.id)
+        .outerjoin(
+            TelegramAccount,
+            and_(
+                TelegramAccount.proxy_id == ProxyInfo.id,
+                TelegramAccount.is_active.is_(True),
+            ),
+        )
+        .where(ProxyInfo.is_active.is_(True))
+        .group_by(ProxyInfo.id)
+        .order_by(func.count(TelegramAccount.id), ProxyInfo.id)
+        .limit(1)
+    )
+    pid = await db_session.scalar(stmt)
+    if pid is None:
+        raise ValueError("系统暂无可用代理 IP,请联系管理员配置。")
+    return pid
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web/routes/accounts.py` around lines 33 - 46, The `_auto_assign_proxy`
function is inefficient because it loads all active proxies and all active
accounts into memory, then counts and sorts them in Python. Instead, use SQL
aggregation to count the number of accounts assigned to each proxy, order by the
count in ascending order, and select only the single proxy with the minimum
assignment count. This approach should use a LEFT JOIN or subquery pattern in
your select statement to count accounts per proxy ID, then limit the result to
retrieve only the single proxy with the lowest count, eliminating the need for
in-memory iteration and sorting.
app/web/routes/admin.py (3)

275-276: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Silent exception swallowing loses debugging context.

The bare except Exception: pass discards all error information, making it difficult to diagnose why valid-looking proxy lines fail to parse.

♻️ Suggested fix to log parsing failures
             if host and port:
                 return {
                     "proxy_host": host,
                     "proxy_port": int(port),
                     "username": username,
                     "password": password,
                     "proxy_type": proxy_type
                 }
-        except Exception:
-            pass
+        except Exception as e:
+            logger.debug("Failed to parse proxy URL '%s': %s", line, e)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web/routes/admin.py` around lines 275 - 276, The bare except Exception:
pass block at lines 275-276 silently discards all exception information without
logging, making it impossible to debug parsing failures. Replace the pass
statement with proper error logging that captures and logs the actual exception
details (including the exception message and type) using the appropriate logger,
so that when proxy line parsing fails, the diagnostic information is preserved
for debugging.

Source: Linters/SAST tools


397-403: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

N+1 query pattern in batch delete.

Each proxy is fetched individually in a loop. For large batches, this creates many round-trips to the database.

♻️ Suggested fix using bulk UPDATE
     if proxy_ids:
-        # 执行软删除:将 is_active 设为 False
-        for pid in proxy_ids:
-            proxy = await db_session.get(ProxyInfo, pid)
-            if proxy:
-                proxy.is_active = False
+        from sqlalchemy import update
+        await db_session.execute(
+            update(ProxyInfo)
+            .where(ProxyInfo.id.in_(proxy_ids))
+            .values(is_active=False)
+        )
         await db_session.commit()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web/routes/admin.py` around lines 397 - 403, The code fetches each proxy
individually in a loop using db_session.get(ProxyInfo, pid), which creates an
N+1 query pattern causing many database round-trips for large batches. Replace
the loop that iterates through proxy_ids and fetches each ProxyInfo individually
with a single bulk UPDATE statement that sets is_active to False for all proxies
whose IDs are in the proxy_ids list, then commit the session once. This will
reduce multiple queries to a single efficient database operation.

61-65: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using UTC for consistent date boundaries.

datetime.now() uses the server's local timezone. If the server timezone differs from the expected user timezone or changes (e.g., DST, deployment to different region), the "today's messages" count may be inconsistent.

♻️ Suggested fix using UTC
-    from datetime import datetime, time
-    today_start = datetime.combine(datetime.now().date(), time.min)
+    from datetime import datetime, time, timezone
+    today_start = datetime.combine(datetime.now(timezone.utc).date(), time.min)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web/routes/admin.py` around lines 61 - 65, Replace the `datetime.now()`
call in the today_start calculation with `datetime.utcnow()` to ensure timezone
consistency. The current code uses the server's local timezone which can cause
inconsistent results if the server timezone differs or changes. Update the line
where `today_start` is created to use UTC time instead of local time, ensuring
the date boundary comparison with TelegramMessage.created_at remains consistent
across all deployments and timezones.
templates/admin/users.html (1)

67-69: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Avoid rendering full API keys by default in the user table.

Exposing full keys for every row increases credential leakage risk in normal admin operations. Mask by default and reveal/copy only on explicit action.

Suggested fix
-                                <code class="text-xs bg-slate-100 px-2 py-1 rounded text-slate-600 font-mono select-all">{{ u.api_key }}</code>
+                                <code class="text-xs bg-slate-100 px-2 py-1 rounded text-slate-600 font-mono">
+                                    {{ (u.api_key[:6] ~ '••••••' ~ u.api_key[-4:]) if u.api_key else '-' }}
+                                </code>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/admin/users.html` around lines 67 - 69, The code element displaying
the full API key in the api-key-{{ u.id }} div exposes the complete credential
by default, creating a security risk. Mask the API key by default (replace the
visible text with dots or asterisks) and only reveal the actual key value on
explicit user action such as a click or button interaction. Consider adding a
separate button or event handler alongside the reset button that toggles
visibility or copies the key to clipboard without keeping it permanently visible
in the table.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/web/dependencies.py`:
- Around line 32-35: The function get_current_admin_from_cookie uses three
undefined names (AsyncSession, get_db_session, and User) which will cause
runtime NameError exceptions. Add the missing import statements at the top of
the file: import AsyncSession from sqlalchemy.ext.asyncio, import get_db_session
from the appropriate database module in your project, and import User from
app.models. These imports must be added before the function definition to make
the referenced names available in the file's namespace.

In `@app/web/routes/accounts.py`:
- Around line 70-73: The code at the proxy_id assignment section (around line
70) and the similar location at line 195 currently accepts proxy_id from client
form data, which bypasses server-side auto-assignment. Remove the logic that
attempts to parse and validate proxy_id from the form input (the int()
conversion of proxy_id parameter), and instead always call
_auto_assign_proxy(db_session) to enforce server-side proxy assignment. Apply
this same change at both locations mentioned (the initial assignment area and
the second location at line 195) to ensure the proxy is always assigned by the
server regardless of what the client submits.

In `@app/web/routes/admin.py`:
- Around line 278-299: The colon-based string splitting in the proxy parsing
logic will incorrectly parse IPv6 addresses because they contain multiple
colons. Before attempting the simple colon split on the line variable, first
check if the string contains IPv6 address format (either with brackets like
[::1]:port or without). For IPv6 addresses with brackets, extract the IPv6
address from within the brackets and the port/credentials after the closing
bracket separately. For IPv6 addresses without brackets, use a different parsing
strategy that handles the multiple colons in the IPv6 address. Only apply the
current colon-splitting logic for non-IPv6 addresses (IPv4 and hostnames).

In `@templates/admin/admin_base.html`:
- Around line 62-70: The aria-expanded attribute on the button with
onclick="toggleAdminMobileMenu()" is hardcoded to "false" and never updates when
the menu toggles, breaking assistive technology accessibility. Modify the
toggleAdminMobileMenu() function to update the aria-expanded attribute on the
button element to "true" when opening the menu and "false" when closing it,
keeping the state synchronized with the actual visibility state of the mobile
menu elements. Apply the same fix to the other mobile menu button mentioned in
lines 131-143.

In `@templates/admin/proxies.html`:
- Around line 49-51: The password input field on line 49-51 has type="text"
which displays passwords in clear text as users type them, posing a security
risk. Change the input type attribute from "text" to "password" to mask the
password input. Additionally, the comment indicates this issue also applies to
line 132, which likely involves rendering passwords in a list or table - locate
that area and ensure proxy credentials are not displayed in plain text there as
well, either by masking the values or hiding them from view.
- Around line 20-21: All POST forms in the proxies template (including those
with actions /web/admin/proxies/add, /web/admin/proxies/import, and
/web/admin/proxies/batch-delete) and HTMX POST requests (toggle-active and
delete) are missing CSRF token protection. Add CSRF token fields to each form by
including a hidden input with the token value, ensure HTMX requests include the
token in request headers or data attributes, and update all corresponding route
handlers to validate the CSRF token before processing the request.
Alternatively, configure SameSite=Strict on session cookies if using a framework
with built-in CSRF protection.

---

Nitpick comments:
In `@app/web/routes/accounts.py`:
- Around line 33-46: The `_auto_assign_proxy` function is inefficient because it
loads all active proxies and all active accounts into memory, then counts and
sorts them in Python. Instead, use SQL aggregation to count the number of
accounts assigned to each proxy, order by the count in ascending order, and
select only the single proxy with the minimum assignment count. This approach
should use a LEFT JOIN or subquery pattern in your select statement to count
accounts per proxy ID, then limit the result to retrieve only the single proxy
with the lowest count, eliminating the need for in-memory iteration and sorting.

In `@app/web/routes/admin.py`:
- Around line 275-276: The bare except Exception: pass block at lines 275-276
silently discards all exception information without logging, making it
impossible to debug parsing failures. Replace the pass statement with proper
error logging that captures and logs the actual exception details (including the
exception message and type) using the appropriate logger, so that when proxy
line parsing fails, the diagnostic information is preserved for debugging.
- Around line 397-403: The code fetches each proxy individually in a loop using
db_session.get(ProxyInfo, pid), which creates an N+1 query pattern causing many
database round-trips for large batches. Replace the loop that iterates through
proxy_ids and fetches each ProxyInfo individually with a single bulk UPDATE
statement that sets is_active to False for all proxies whose IDs are in the
proxy_ids list, then commit the session once. This will reduce multiple queries
to a single efficient database operation.
- Around line 61-65: Replace the `datetime.now()` call in the today_start
calculation with `datetime.utcnow()` to ensure timezone consistency. The current
code uses the server's local timezone which can cause inconsistent results if
the server timezone differs or changes. Update the line where `today_start` is
created to use UTC time instead of local time, ensuring the date boundary
comparison with TelegramMessage.created_at remains consistent across all
deployments and timezones.

In `@templates/admin/users.html`:
- Around line 67-69: The code element displaying the full API key in the
api-key-{{ u.id }} div exposes the complete credential by default, creating a
security risk. Mask the API key by default (replace the visible text with dots
or asterisks) and only reveal the actual key value on explicit user action such
as a click or button interaction. Consider adding a separate button or event
handler alongside the reset button that toggles visibility or copies the key to
clipboard without keeping it permanently visible in the table.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c5ce1ff-c4c7-4336-9ff0-d01ffb0830ad

📥 Commits

Reviewing files that changed from the base of the PR and between 01e690c and 8dccae9.

📒 Files selected for processing (13)
  • app/service/auth_service.py
  • app/web/__init__.py
  • app/web/dependencies.py
  • app/web/routes/accounts.py
  • app/web/routes/admin.py
  • app/web/routes/auth.py
  • templates/accounts/login_flow.html
  • templates/admin/admin_base.html
  • templates/admin/dashboard.html
  • templates/admin/proxies.html
  • templates/admin/select_role.html
  • templates/admin/users.html
  • templates/base.html

Comment thread app/web/dependencies.py
Comment thread app/web/routes/accounts.py Outdated
Comment thread app/web/routes/admin.py Outdated
Comment thread templates/admin/admin_base.html Outdated
Comment on lines +20 to +21
<form action="/web/admin/proxies/add" method="POST" class="space-y-4">
<div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e

echo "1) Locate CSRF token generation/validation paths"
rg -n -C2 -i '\bcsrf\b|xsrf|x-csrf|csrf_token|csrftoken' --type=py --type=html

echo "2) Check cookie security attributes in auth/session handling"
rg -n -C2 -i 'set_cookie\(|samesite|httponly|secure' --type=py

echo "3) Inspect admin POST routes and their guards"
rg -n -C2 '`@router`\.post\("/admin' app/web/routes/admin.py

Repository: dunea/telegram-auto-message-server

Length of output: 127


🏁 Script executed:

#!/bin/bash
# Explore repository structure to understand layout
echo "=== Repository structure ==="
git ls-files | head -30
echo ""

# Check if the template file exists
echo "=== Looking for proxies template ==="
find . -name "proxies.html" -type f 2>/dev/null || echo "File not found"
echo ""

# Find all HTML templates to understand structure
echo "=== All HTML template files ==="
find . -name "*.html" -type f 2>/dev/null | head -20

Repository: dunea/telegram-auto-message-server

Length of output: 1533


🏁 Script executed:

#!/bin/bash
# Look for Python app files
echo "=== Looking for app/main structure ==="
find . -path "*/app/*.py" -o -path "*/routes/*.py" -o -path "*/web/*.py" | head -20
echo ""

# Check admin.py if it exists
if [ -f "app/web/routes/admin.py" ]; then
  echo "=== Found admin.py, checking for POST routes ==="
  wc -l app/web/routes/admin.py
else
  find . -name "admin.py" -type f 2>/dev/null
fi

Repository: dunea/telegram-auto-message-server

Length of output: 739


🏁 Script executed:

#!/bin/bash
# Search for CSRF/XSRF patterns more broadly
echo "=== Searching for CSRF-related code (broader search) ==="
rg -i 'csrf|xsrf|x-csrf|token.*form|form.*token' -t py -t html -t js 2>/dev/null | head -50 || echo "No CSRF patterns found"

Repository: dunea/telegram-auto-message-server

Length of output: 50391


🏁 Script executed:

#!/bin/bash
# Read the proxies template to check for CSRF tokens
echo "=== Reading templates/admin/proxies.html ==="
cat templates/admin/proxies.html

Repository: dunea/telegram-auto-message-server

Length of output: 13156


🏁 Script executed:

#!/bin/bash
# Read admin.py routes to check for CSRF protection on POST endpoints
echo "=== Checking admin.py file structure ==="
head -100 app/web/routes/admin.py
echo ""
echo "=== Looking for POST route decorators and CSRF patterns ==="
grep -n "post\|csrf\|CSRF\|@" app/web/routes/admin.py | head -40

Repository: dunea/telegram-auto-message-server

Length of output: 4914


🏁 Script executed:

#!/bin/bash
# Look for Flask/web framework setup and CSRF middleware
echo "=== Looking for CSRF middleware/extension setup ==="
find . -name "*.py" -path "*/app/*" ! -path "*/alembic/*" | xargs grep -l "csrf\|CSRF" 2>/dev/null | head -10
echo ""
echo "=== Check main app initialization ==="
cat app/__init__.py

Repository: dunea/telegram-auto-message-server

Length of output: 439


Add CSRF token validation to all admin POST endpoints.

The template contains multiple POST forms (/web/admin/proxies/add, /web/admin/proxies/import, /web/admin/proxies/batch-delete) and HTMX POST requests (toggle-active, delete) without CSRF tokens, while the backend uses cookie-based authentication with no CSRF protection mechanism. Implement CSRF token generation in forms and validation in route handlers, or enable SameSite=Strict on session cookies as a mitigation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/admin/proxies.html` around lines 20 - 21, All POST forms in the
proxies template (including those with actions /web/admin/proxies/add,
/web/admin/proxies/import, and /web/admin/proxies/batch-delete) and HTMX POST
requests (toggle-active and delete) are missing CSRF token protection. Add CSRF
token fields to each form by including a hidden input with the token value,
ensure HTMX requests include the token in request headers or data attributes,
and update all corresponding route handlers to validate the CSRF token before
processing the request. Alternatively, configure SameSite=Strict on session
cookies if using a framework with built-in CSRF protection.

Comment thread templates/admin/proxies.html

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/web/routes/accounts.py (2)

63-80: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider removing unused proxy_id parameter.

The proxy_id: str = Form("") parameter on line 65 is now dead code since the function unconditionally calls _auto_assign_proxy(). Removing it clarifies the API contract and eliminates the misleading suggestion that clients can influence proxy selection.

♻️ Suggested cleanup
 async def request_phone_code(
     phone_number: str = Form(...),
-    proxy_id: str = Form(""),
     telegram_service: TelegramService = Depends(get_telegram_service),
     user_id: int = Depends(get_current_user_from_cookie),
     db_session: AsyncSession = Depends(get_db_session),
 ):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web/routes/accounts.py` around lines 63 - 80, The `proxy_id` parameter in
the `request_phone_code` function signature is unused dead code since the
function unconditionally calls `_auto_assign_proxy()` without referencing it.
Remove the `proxy_id: str = Form("")` parameter from the function definition to
clarify the API contract and eliminate the misleading indication that clients
can influence proxy selection.

185-203: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Same cleanup opportunity: remove unused proxy_id parameter.

Same as request_phone_code above—the proxy_id parameter on line 188 is unused and can be removed.

♻️ Suggested cleanup
 async def login_with_session(
     phone_number: str = Form(...),
     session_string: str = Form(...),
-    proxy_id: str = Form(""),
     telegram_service: TelegramService = Depends(get_telegram_service),
     user_id: int = Depends(get_current_user_from_cookie),
     db_session: AsyncSession = Depends(get_db_session),
 ):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web/routes/accounts.py` around lines 185 - 203, The `proxy_id` parameter
in the `login_with_session` function is declared but never used in the function
body. Remove the unused `proxy_id: str = Form("")` parameter from the function
signature to clean up the code and improve maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/web/routes/accounts.py`:
- Around line 63-80: The `proxy_id` parameter in the `request_phone_code`
function signature is unused dead code since the function unconditionally calls
`_auto_assign_proxy()` without referencing it. Remove the `proxy_id: str =
Form("")` parameter from the function definition to clarify the API contract and
eliminate the misleading indication that clients can influence proxy selection.
- Around line 185-203: The `proxy_id` parameter in the `login_with_session`
function is declared but never used in the function body. Remove the unused
`proxy_id: str = Form("")` parameter from the function signature to clean up the
code and improve maintainability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e822d37b-b0c7-48da-b977-cc789736064b

📥 Commits

Reviewing files that changed from the base of the PR and between 8dccae9 and a6aa27a.

📒 Files selected for processing (8)
  • app/web/dependencies.py
  • app/web/routes/accounts.py
  • app/web/routes/admin.py
  • app/web/routes/auth.py
  • templates/admin/admin_base.html
  • templates/admin/proxies.html
  • templates/admin/users.html
  • templates/base.html
🚧 Files skipped from review as they are similar to previous changes (6)
  • app/web/dependencies.py
  • templates/base.html
  • templates/admin/admin_base.html
  • app/web/routes/auth.py
  • templates/admin/proxies.html
  • templates/admin/users.html

@dunea dunea merged commit 47fe345 into main Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant