Skip to main content

Plan: Production Admin Panel Access (Discord OAuth + First-User Fallback)

1. Goal

  • Primary: Admin panel access in production via Discord OAuth (no code deploy to change who can log in).
  • Fallback: First-user bootstrap via username/password so /admin works before any Discord user exists.
  • Link identity: Users created via Discord or local bootstrap are stored (User + Identity); permissions come from role assignments (e.g. TenantAdmin → admin.access).

2. Current Behavior (Summary)

ItemBehavior
Admin entrypointADMIN_AUTH_ENTRYPOINT: if ADMIN_AUTH_USE_LOCAL/api/auth/local/start?tenantId=default; else → Discord OAuth start with postLoginRedirect=/admin.
Discord OAuthStart → redirect to Discord → callback /api/auth/discord/callbackensureUserAndIdentity (create/link User + Identity) → resolvePermissions (from role assignments) → session created. New Discord users have no role assignmentspermissionKeys = [] → 403 on /admin.
Local loginSame flow + ensureLocalAdminAccess: if username matches LOCAL_AUTH_DEFAULT_USER, seed default roles and assign TenantAdmin to that user → they get admin.access.
Production (render.yaml)LOCAL_AUTH_ENABLED=false, ADMIN_AUTH_USE_LOCAL=false → Discord-only; Discord provider is upserted when DISCORD_CLIENT_ID/DISCORD_CLIENT_SECRET are set.

3. Production OAuth Setup (Steps First)

Do these before relying on Discord for admin in production.

3.1 Discord Developer Portal

  1. Go to Discord Developer Portal → your application (or create one).
  2. OAuth2 → Redirects: Add the exact callback URL (no trailing slash):
    https://<your-render-service>.onrender.com/api/auth/discord/callback
    Example: https://ledgerline-api.onrender.com/api/auth/discord/callback
  3. OAuth2: Note Client ID and Client Secret (reset secret if needed).

3.2 Render Dashboard

In the ledgerline-api service → Environment:

VariableValueNotes
DISCORD_REDIRECT_URIhttps://<your-service>.onrender.com/api/auth/discord/callbackMust match Discord redirects exactly.
DISCORD_CLIENT_IDFrom Discord OAuth2
DISCORD_CLIENT_SECRETFrom Discord OAuth2
AUTH_COOKIE_SECRETStrong secretUsed to sign session cookie.
MONGO_URLProduction MongoDBRequired for users/roles/identities.

Keep for production (Discord-first):

  • ADMIN_AUTH_USE_LOCAL = false (admin entrypoint = Discord OAuth).
  • LOCAL_AUTH_ENABLED = false unless you enable bootstrap (see below).

3.3 After env changes

Redeploy so the app picks up the new env. Then open:

https://<your-service>.onrender.com/admin

You should be redirected to Discord, then back to /admin after login. Before the code change below, the first Discord user would get 403 (no roles). After the code change, the first Discord user gets TenantAdmin and can access admin.


4. Code Changes

4.1 First Discord user gets admin (no roles → TenantAdmin)

Problem: New Discord users have no role assignments, so resolvePermissions returns [] and admin returns 403.

Change: On Discord OAuth callback, after ensureUserAndIdentity, if the user has no permission keys, treat as “first admin” for the tenant: seed default roles and assign TenantAdmin to this user.

  • Where: auth.endpoint.ts – in the /auth/:provider/callback handler, after ensureUserAndIdentity and before sendSession.
  • Logic: Call resolvePermissions(tenantId, userId). If length === 0, call the same logic as ensureLocalAdminAccess but for this userId (seed default roles, get TenantAdmin by name, assign to user). Then call sendSession as usual.

This gives the first Discord user (and any Discord user with no roles) TenantAdmin so /admin works.

4.2 Fallback username/password for first user

Goal: Allow one-time (or first-user) login with username/password so /admin works before any Discord login.

Options:

  • A) Bootstrap flag (recommended):

    • Add ADMIN_BOOTSTRAP_LOCAL_ENABLED (default false).
    • When true: keep ADMIN_AUTH_USE_LOCAL=false so the default entrypoint remains Discord, but also allow local login at a dedicated path, e.g. /api/auth/local/start?tenantId=default&mode=redirect&postLoginRedirect=/admin&bootstrap=1.
    • When bootstrap local login is used with credentials that match LOCAL_AUTH_DEFAULT_USER / LOCAL_AUTH_DEFAULT_PASSWORD, create session and run existing ensureLocalAdminAccess (seed roles, assign TenantAdmin).
    • In production: set LOCAL_AUTH_ENABLED=true and ADMIN_BOOTSTRAP_LOCAL_ENABLED=true only for initial setup; after first admin exists, set ADMIN_BOOTSTRAP_LOCAL_ENABLED=false (and optionally LOCAL_AUTH_ENABLED=false).
  • B) Reuse existing local entrypoint:

    • For first-time setup, set ADMIN_AUTH_USE_LOCAL=true and LOCAL_AUTH_ENABLED=true.
    • Set LOCAL_AUTH_DEFAULT_USER and LOCAL_AUTH_DEFAULT_PASSWORD to a strong bootstrap password.
    • Open /admin → redirects to local login → log in once → ensureLocalAdminAccess gives TenantAdmin.
    • Then set ADMIN_AUTH_USE_LOCAL=false (and optionally LOCAL_AUTH_ENABLED=false) and use Discord only.

Recommendation: Implement 4.1 (first Discord user gets TenantAdmin) and document option B for fallback (no new env if you prefer). Optionally add option A with ADMIN_BOOTSTRAP_LOCAL_ENABLED and a “First-time setup (username/password)” link on a small login/entry page when the flag is true.

4.3 Ensure user is “set up” and linked to OAuth identity

  • Discord: Already done. ensureUserAndIdentity creates/links User and Identity (provider discord, providerUserId). Permissions come from role assignments (e.g. TenantAdmin). After 4.1, first Discord user gets TenantAdmin so they are “set up” for admin.
  • Local bootstrap: User + Identity are created in ensureUserAndIdentity (provider local). ensureLocalAdminAccess assigns TenantAdmin when username matches LOCAL_AUTH_DEFAULT_USER. So the “first user” is already linked (local identity) and has admin.

No extra “link to OAuth identity” step is required beyond the existing User + Identity + role assignment flow.


5. Step-by-Step Checklist

Production OAuth (Discord) – do first

  1. Create or select Discord application; add redirect https://<render-service>.onrender.com/api/auth/discord/callback.
  2. Set Render env: DISCORD_REDIRECT_URI, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, AUTH_COOKIE_SECRET, MONGO_URL.
  3. Keep ADMIN_AUTH_USE_LOCAL=false (and LOCAL_AUTH_ENABLED=false for Discord-only).
  4. Deploy; open /admin → redirect to Discord → log in.
  5. After code change 4.1: first Discord user gets TenantAdmin and can access /admin.

First-user fallback (username/password)

Option B (no new code):

  1. Set ADMIN_AUTH_USE_LOCAL=true and LOCAL_AUTH_ENABLED=true in Render.
  2. Set LOCAL_AUTH_DEFAULT_USER and LOCAL_AUTH_DEFAULT_PASSWORD to a strong password (e.g. random).
  3. Deploy; open /admin → local login → log in once → TenantAdmin assigned.
  4. Set ADMIN_AUTH_USE_LOCAL=false (and optionally LOCAL_AUTH_ENABLED=false) for normal Discord-only use.

Option A (if implemented):

  1. Set ADMIN_BOOTSTRAP_LOCAL_ENABLED=true and LOCAL_AUTH_ENABLED=true.
  2. Use “First-time setup” link (or direct local start URL) with bootstrap credentials; after login, disable bootstrap flag.

6. Env Reference

VariableProduction (Discord-first)Bootstrap / first user
DISCORD_REDIRECT_URIhttps://...onrender.com/api/auth/discord/callbackSame
DISCORD_CLIENT_IDSetSame
DISCORD_CLIENT_SECRETSetSame
ADMIN_AUTH_USE_LOCALfalsetrue only for option B first login
LOCAL_AUTH_ENABLEDfalsetrue when using local bootstrap
LOCAL_AUTH_DEFAULT_USERStrong username for bootstrap
LOCAL_AUTH_DEFAULT_PASSWORDStrong password for bootstrap
ADMIN_BOOTSTRAP_LOCAL_ENABLEDtrue only for option A bootstrap

7. Risks and Mitigations

  • Cookie domain / HTTPS: Render serves HTTPS; set AUTH_COOKIE_SECRET and rely on existing secureCookie: IS_PRODUCTION so the session cookie is secure.
  • First Discord user is admin: By design; restrict who is invited to your Discord or who can use the app. Optionally add a “bootstrap admin list” (e.g. Discord IDs) in a later iteration.
  • Bootstrap credentials in env: Use strong, unique values and rotate after first use if desired; disable local/bootstrap when not needed.