feat(deps): add require_active_subscription guard with allowlist
Mounts on Pro routers (trees, sessions, scripts, FlowPilot, etc.) and returns 402 with structured detail when an account's subscription is missing or locked. Allowlist bypasses billing/account/auth flows so users can recover from a lapsed subscription. Conftest now seeds a default Pro/active Subscription on test_user and test_admin (delete-then-insert because the register endpoint already creates a free/active sub by default). Two existing tests adapted to the new seeded plan; tenant-isolation tests seed Subscription rows for the accounts they create directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_tenant_context
|
||||
from app.api.deps import require_tenant_context, require_active_subscription
|
||||
from app.api.endpoints import (
|
||||
admin,
|
||||
admin_audit,
|
||||
@@ -102,23 +102,32 @@ api_router.include_router(admin_survey.router)
|
||||
api_router.include_router(admin_gallery.router)
|
||||
# ---------------------------------------------------------------------------
|
||||
# User-facing endpoints — tenant context required
|
||||
#
|
||||
# _tenant_deps: routers that only require an authenticated user inside a
|
||||
# tenant (auth/account/admin/non-Pro feature surfaces).
|
||||
# _pro_deps: routers gated behind an active Pro subscription. Adds
|
||||
# require_active_subscription which raises 402 unless the
|
||||
# account's Subscription is active/complimentary/past_due or
|
||||
# trialing-with-time-remaining. Allowlisted paths in deps.py
|
||||
# bypass the gate for billing/account admin/auth flows.
|
||||
# ---------------------------------------------------------------------------
|
||||
_tenant_deps = [Depends(require_tenant_context)]
|
||||
_pro_deps = [Depends(require_tenant_context), Depends(require_active_subscription)]
|
||||
|
||||
api_router.include_router(trees.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(trees.router, dependencies=_pro_deps)
|
||||
api_router.include_router(sidebar.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(sessions.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(sessions.router, dependencies=_pro_deps)
|
||||
api_router.include_router(invite.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(categories.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(tags.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(folders.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(step_categories.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(steps.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(step_categories.router, dependencies=_pro_deps)
|
||||
api_router.include_router(steps.router, dependencies=_pro_deps)
|
||||
api_router.include_router(accounts.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(shares.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(tree_markdown.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(ratings.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(analytics.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(analytics.router, dependencies=_pro_deps)
|
||||
api_router.include_router(target_lists.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(maintenance_schedules.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(feedback.router, dependencies=_tenant_deps)
|
||||
@@ -126,31 +135,31 @@ api_router.include_router(ai_builder.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(ai_fix.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(ai_chat.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(copilot.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(assistant_chat.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(assistant_chat.router, dependencies=_pro_deps)
|
||||
api_router.include_router(tree_transfer.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(ai_suggestions.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(kb_accelerator.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(scripts.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(integrations.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(scripts.router, dependencies=_pro_deps)
|
||||
api_router.include_router(integrations.router, dependencies=_pro_deps)
|
||||
api_router.include_router(onboarding.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(branding.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(supporting_data.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(network_diagrams.router, dependencies=_tenant_deps)
|
||||
# session_handoffs queue router must come before ai_sessions to avoid conflict
|
||||
api_router.include_router(session_handoffs.queue_router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_resolutions.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_handoffs.queue_router, dependencies=_pro_deps)
|
||||
api_router.include_router(session_resolutions.router, dependencies=_pro_deps)
|
||||
# session_facts mounts under /ai-sessions/{id}/facts — register before ai_sessions
|
||||
# so the {session_id}/facts subpaths take precedence over any future generic catchalls.
|
||||
api_router.include_router(session_facts.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_suggested_fixes.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_facts.router, dependencies=_pro_deps)
|
||||
api_router.include_router(session_suggested_fixes.router, dependencies=_pro_deps)
|
||||
api_router.include_router(draft_templates.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(ai_sessions.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(flow_proposals.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(flowpilot_analytics.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(ai_sessions.router, dependencies=_pro_deps)
|
||||
api_router.include_router(flow_proposals.router, dependencies=_pro_deps)
|
||||
api_router.include_router(flowpilot_analytics.router, dependencies=_pro_deps)
|
||||
api_router.include_router(notifications.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(uploads.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(script_builder.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(script_builder.router, dependencies=_pro_deps)
|
||||
api_router.include_router(beta_feedback.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_branches.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_handoffs.router, dependencies=_tenant_deps)
|
||||
api_router.include_router(session_branches.router, dependencies=_pro_deps)
|
||||
api_router.include_router(session_handoffs.router, dependencies=_pro_deps)
|
||||
api_router.include_router(device_types.router, dependencies=_tenant_deps)
|
||||
|
||||
Reference in New Issue
Block a user