Phase 3 harden CSRF invalid POST and work access handling

This commit is contained in:
A R R R Associates
2026-06-26 11:41:00 +05:30
parent 4ce5556e9d
commit fc31a5f4e9
7 changed files with 503 additions and 94 deletions
+101 -14
View File
@@ -48,6 +48,12 @@ from app.modules.platform_billing.services import (
router = APIRouter(prefix="/platform-billing", tags=["platform-billing-ui"])
def _csrf_rejected(request: Request):
from app.core.http_responses import forbidden_response
return forbidden_response(request, "CSRF validation failed")
def _redirect_denied():
from app.core.http_responses import ui_access_denied
return ui_access_denied()
@@ -121,6 +127,45 @@ def dashboard(request: Request):
db.close()
# Phase 3 security hardening: list pages must not accept direct unsafe POSTs.
# Existing create/update features continue to use their dedicated /new or action routes.
@router.post("/plans")
def plans_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/accounts")
def accounts_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/audit-firm-subscriptions")
def audit_firm_subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/client-dashboard-subscriptions")
def client_dashboard_subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/consultant-subscriptions")
def consultant_subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/subscriptions")
def subscriptions_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.post("/invoices")
def invoices_list_post_rejected(request: Request):
return _csrf_rejected(request)
@router.get("/plans")
def plans_list(request: Request, q: str = ""):
db = CommonSessionLocal()
@@ -152,7 +197,10 @@ def plan_create(request: Request, csrf_token: str = Form(...), code: str = Form(
user, response = _require_user(request, db, "platform_plans.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_plan(db, code=code, name=name, target_account_type=target_account_type, billing_cycle=billing_cycle, base_amount=base_amount, gst_rate=gst_rate, description=description, feature_text=feature_text)
return RedirectResponse(url="/platform-billing/plans", status_code=303)
finally:
@@ -190,7 +238,10 @@ def account_create(request: Request, csrf_token: str = Form(...), account_type:
user, response = _require_user(request, db, "platform_billing.create")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_account(db, account_type=account_type, account_code=account_code, display_name=display_name, tenant_id=int(tenant_id) if tenant_id else None, client_id=int(client_id) if client_id else None, consultant_id=int(consultant_id) if consultant_id else None, email=email, mobile=mobile, gstin=gstin, pan=pan, billing_address=billing_address, state=state, notes=notes, user_id=user.id)
return RedirectResponse(url="/platform-billing/accounts", status_code=303)
finally:
@@ -228,7 +279,10 @@ def audit_firm_accounts_sync(request: Request, csrf_token: str = Form(...)):
user, response = _require_user(request, db, "platform_subscriptions.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = sync_audit_firm_billing_accounts(db, user_id=user.id)
return RedirectResponse(url=f"/platform-billing/audit-firm-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303)
finally:
@@ -264,7 +318,10 @@ def audit_firm_generate_submit(request: Request, csrf_token: str = Form(...), su
user, response = _require_user(request, db, "platform_billing.generate")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = generate_audit_firm_subscription_invoices(
db,
subscription_ids=subscription_ids,
@@ -329,7 +386,10 @@ def client_dashboard_accounts_sync(request: Request, csrf_token: str = Form(...)
user, response = _require_user(request, db, "platform_subscriptions.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = sync_client_dashboard_billing_accounts(db, user_id=user.id)
return RedirectResponse(url=f"/platform-billing/client-dashboard-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303)
finally:
@@ -343,7 +403,10 @@ def client_dashboard_subscription_create(request: Request, csrf_token: str = For
user, response = _require_user(request, db, "platform_subscriptions.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_subscription(
db,
account_id=account_id,
@@ -392,7 +455,10 @@ def client_dashboard_generate_submit(request: Request, csrf_token: str = Form(..
user, response = _require_user(request, db, "platform_billing.generate")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = generate_client_dashboard_subscription_invoices(
db,
subscription_ids=subscription_ids,
@@ -456,7 +522,10 @@ def consultant_accounts_sync(request: Request, csrf_token: str = Form(...)):
user, response = _require_user(request, db, "platform_subscriptions.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = sync_consultant_billing_accounts(db, user_id=user.id)
return RedirectResponse(url=f"/platform-billing/consultant-subscriptions?synced=created-{result['created']}-updated-{result['updated']}", status_code=303)
finally:
@@ -470,7 +539,10 @@ def consultant_subscription_create(request: Request, csrf_token: str = Form(...)
user, response = _require_user(request, db, "platform_subscriptions.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_subscription(
db,
account_id=account_id,
@@ -519,7 +591,10 @@ def consultant_generate_submit(request: Request, csrf_token: str = Form(...), su
user, response = _require_user(request, db, "platform_billing.generate")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
result = generate_consultant_subscription_invoices(
db,
subscription_ids=subscription_ids,
@@ -580,7 +655,10 @@ def subscription_create(request: Request, csrf_token: str = Form(...), account_i
user, response = _require_user(request, db, "platform_subscriptions.manage")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
create_platform_subscription(db, account_id=account_id, plan_id=plan_id, subscription_code=subscription_code, start_date=parse_date(start_date), end_date=parse_date(end_date), billing_cycle=billing_cycle, amount=amount, gst_rate=gst_rate, auto_generate_invoice=auto_generate_invoice == "on", notes=notes, user_id=user.id)
return RedirectResponse(url="/platform-billing/subscriptions", status_code=303)
finally:
@@ -618,7 +696,10 @@ def invoice_create(request: Request, csrf_token: str = Form(...), account_id: in
user, response = _require_user(request, db, "platform_billing.create")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
invoice = create_platform_invoice(
db,
account_id=account_id,
@@ -660,7 +741,10 @@ def invoice_post(request: Request, invoice_id: int, csrf_token: str = Form(...))
user, response = _require_user(request, db, "platform_billing.post")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
invoice = get_platform_invoice(db, invoice_id)
if invoice and invoice.status == "DRAFT":
post_platform_invoice(db, invoice, user.id)
@@ -676,7 +760,10 @@ def payment_create(request: Request, invoice_id: int, csrf_token: str = Form(...
user, response = _require_user(request, db, "platform_billing.payment.create")
if response:
return response
validate_csrf(request, csrf_token)
try:
validate_csrf(request, csrf_token)
except PermissionError:
return _csrf_rejected(request)
invoice = get_platform_invoice(db, invoice_id)
if invoice:
record_platform_payment(db, invoice=invoice, amount=amount, mode=mode, reference_no=reference_no, notes=notes, user_id=user.id)