Skip to content

fix(security): require auth token on partner & admin endpoints - #2

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1783177285-secure-privileged-endpoints
Open

fix(security): require auth token on partner & admin endpoints#2
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1783177285-secure-privileged-endpoints

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Security scan of the codebase. The one critical issue was broken access control: every partner and admin endpoint was completely unauthenticated. Anyone on the internet could read all customer PII (name + phone) via GET /admin/orders and GET /partners/{id}/orders, issue arbitrary refunds via POST /admin/refund/{id}, mark orders redeemed via POST /redeem, and publish boxes as any partner via POST /boxes.

This PR adds fail-closed, token-based auth to those endpoints. The customer-facing storefront (catalog, booking, payment, "my orders") stays public.

What changed

New app/auth.py with two header-based dependencies backed by env vars:

def _require(provided, env_name, role):
    expected = os.environ.get(env_name)
    if not expected:
        raise HTTPException(503, ...)          # fail closed: unset => endpoint disabled
    if not provided or not secrets.compare_digest(provided, expected):
        raise HTTPException(401, ...)

require_admin   -> checks X-Admin-Token   against ADMIN_TOKEN
require_partner -> checks X-Partner-Token against PARTNER_TOKEN

Wired as dependencies=[Depends(require_partner|require_admin)] on:

  • Partner: POST /boxes, GET /partners/{id}/orders, POST /redeem
  • Admin: GET /admin/stats, GET /admin/orders, POST /admin/refund/{id}

Frontend (index.html) prompts for the token when switching to the Partner/Admin tab, stores it in localStorage, and sends it as a header on every request (clears it on 401). render.yaml gets generateValue: true for both tokens so prod is secure by default; README documents the scheme.

Uses secrets.compare_digest (constant-time) and fail-closed defaults so a misconfigured deploy denies rather than exposes.

Scan results for the rest

  • SQL injection — none; all queries in db.py are parameterized.
  • XSS — frontend escapes all interpolated data via esc(); QR SVG is server-generated.
  • Hardcoded secrets — none found.
  • CORS — no CORSMiddleware configured, so default same-origin; not misconfigured.
  • Debug endpointsPOST /admin/seed was already gated by local_only.

Follow-ups (not in this PR)

  • Per-partner authorization: a valid PARTNER_TOKEN currently grants access to any partner's data; real per-partner credentials are a follow-up.
  • Dependencies use unbounded >= ranges — consider pinning for supply-chain safety.

Testing

  • Added tests/test_auth.py (added httpx for TestClient): storefront stays public; admin/partner endpoints return 503 when the env token is unset, 401 on missing/wrong token, 200 with the correct token.
  • pytest — 13 passed.

Link to Devin session: https://app.devin.ai/sessions/fe1a1300a37e40c2b18cc8f1793426c8
Requested by: @wpalish

Adds fail-closed token auth (ADMIN_TOKEN / PARTNER_TOKEN via headers) to
privileged endpoints that were fully unauthenticated: admin stats/orders/
refund and partner box-create/orders/redeem. Previously anyone could read
all customer PII, issue refunds, and redeem codes.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@wpalish wpalish self-assigned this Jul 4, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

wpalish pushed a commit that referenced this pull request Jul 16, 2026
…арточка); +контраст футера

По UX-ревью:
- #1 localStorage-нотис был плавающей центр-карточкой (читалось как «глюк»
  поверх секций) → тонкая полоса во всю ширину, приклеена к низу (над bottom-nav
  на мобиле, к краю на десктопе). Показ один раз (флаг ym_cookies уже был).
- #6 контраст копирайта/FAQ-текста в футере поднят (.6→.78, .7→.82 opacity) —
  запас под WCAG AA.
Уже было в коде: #4 срочность («🔥 осталось N» + окно выдачи), #7 соцсети на
светлом --on-ink (не бледные). Требуют ассетов (отдельно): #2 превью карты,
#3 логотипы партнёров/отзывы, #5 лого-марка, #8 PWA-бейджи.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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