Skip to content

Add admin UI for platform credentials (env-dominant resolution)#60

Merged
JanSchm merged 1 commit into
mainfrom
feat/platform-credentials-admin
Jun 9, 2026
Merged

Add admin UI for platform credentials (env-dominant resolution)#60
JanSchm merged 1 commit into
mainfrom
feat/platform-credentials-admin

Conversation

@JanSchm

@JanSchm JanSchm commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Makes the Django admin usable for entering per-organization platform API credentials and wires them into the runtime, while keeping .env authoritative.

  • is_configured is derived on save from a per-platform required-keys map (TikTok client_key, Meta/Pinterest app_id/app_secret aliases, …), so a row only activates when it's actually usable.
  • New PlatformCredentialAdminForm — a real forms.JSONField (avoids a Python-repr corruption trap on the encrypted field), validates required keys, and limits platform choices to credential-bearing platforms. The admin is restricted to superusers (editing reveals decrypted secrets).
  • One env-dominant resolver resolve_platform_credentials(platform, org_id).env wins when complete, otherwise the org's admin-entered row. All five credential-resolution call sites (social_accounts/views.py, social_accounts/tasks.py, publisher/engine.py, composer/views.py, analytics/tasks.py) route through it (they were DB-first before).
  • Webhook hardening — the Meta webhook HMAC is verified against the owning org's secret per account (resolve_app_secret), so one org's secret can't forge events into another org's accounts.
  • Removed the dormant /credentials/ placeholder (view, url, template, and the config/urls.py include); the apps.credentials app itself stays.
  • Docs — README + .env.example describe the real admin path ({APP_URL}/admin/ → Credentials → Platform credentials, superuser only), the env-dominant precedence rule, and how to create a superuser.

Why?

The README advertised a "Settings → Platform Credentials" admin UI that never worked: the page was a "coming soon" placeholder, the admin forced the credentials field read-only, and nothing ever set the is_configured flag the runtime gates on — so the DB path was dead and credentials could only be supplied via .env. This closes that gap (self-hosters can configure credentials per org from the admin) without changing behavior for existing .env deployments.

How to test

  1. pytest apps/credentials apps/inbox/tests/test_webhooks.py — covers derivation, env-dominant resolution + per-platform .env backwards-compat, the admin form round-trip, superuser-only gating, and cross-tenant webhook isolation.
  2. Manual: with SECRET_KEY + ENCRYPTION_KEY_SALT set, run python manage.py createsuperuser, sign in at {APP_URL}/admin/Credentials → Platform credentials, add e.g. a youtube row {"client_id": "...", "client_secret": "..."} → it saves and shows is_configured = True. With no YouTube .env vars the connect flow uses the DB creds; set the .env vars and .env takes precedence.
  3. Existing .env-only deployments are unchanged — every platform's real env key shape still resolves (parametrized backwards-compat tests).

Checklist

  • Tests pass (pytest) — full suite green
  • Lint passes (ruff check and ruff format --check) on changed files
  • Documentation updated (README + .env.example)

Note: no schema change — is_configured already exists, so no new migration.

The README documented a "Settings → Platform Credentials" admin UI, but it never
worked: the page was a "coming soon" placeholder, the Django admin forced the
credentials field read-only, and nothing ever set the `is_configured` flag the
runtime gates on — so the DB-credential path was dead and credentials could only
come from `.env`.

This makes the existing Django admin usable and wires per-org DB credentials into
the runtime, while keeping `.env` authoritative.

- PlatformCredential.save() derives `is_configured` from the credential values via
  a per-platform required-keys map (TikTok `client_key`, Meta/Pinterest `app_id`
  aliases, etc.), so a row only activates when it is actually usable.
- New PlatformCredentialAdminForm: a real `forms.JSONField` (avoids a Python-repr
  corruption trap on the encrypted field), validates required keys, limits the
  platform choices to credential-bearing platforms. Admin restricted to
  superusers (editing reveals decrypted secrets).
- resolve_platform_credentials(platform, org_id): one env-dominant resolver (env
  wins when complete, else the org's admin row). All five credential-resolution
  call sites route through it (they were DB-first before).
- Webhooks: verify the Meta HMAC per owning-org secret (resolve_app_secret) so
  one org's secret cannot forge events into another org's accounts.
- Remove the dormant /credentials/ placeholder (view, url, template, include).
- Docs: README + .env.example reflect the real admin path, the env-dominant
  precedence rule, and how to create a superuser.

Tests: per-platform env backwards-compat, cross-tenant webhook isolation, admin
superuser gating, resolver precedence. Full suite green; ruff clean; no migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@JanSchm JanSchm merged commit 7788f65 into main Jun 9, 2026
5 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