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
/adminworks 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)
| Item | Behavior |
|---|---|
| Admin entrypoint | ADMIN_AUTH_ENTRYPOINT: if ADMIN_AUTH_USE_LOCAL → /api/auth/local/start?tenantId=default; else → Discord OAuth start with postLoginRedirect=/admin. |
| Discord OAuth | Start → redirect to Discord → callback /api/auth/discord/callback → ensureUserAndIdentity (create/link User + Identity) → resolvePermissions (from role assignments) → session created. New Discord users have no role assignments → permissionKeys = [] → 403 on /admin. |
| Local login | Same 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
- Go to Discord Developer Portal → your application (or create one).
- OAuth2 → Redirects: Add the exact callback URL (no trailing slash):
Example:https://<your-render-service>.onrender.com/api/auth/discord/callback
https://ledgerline-api.onrender.com/api/auth/discord/callback - OAuth2: Note Client ID and Client Secret (reset secret if needed).
3.2 Render Dashboard
In the ledgerline-api service → Environment:
| Variable | Value | Notes |
|---|---|---|
DISCORD_REDIRECT_URI | https://<your-service>.onrender.com/api/auth/discord/callback | Must match Discord redirects exactly. |
DISCORD_CLIENT_ID | From Discord OAuth2 | |
DISCORD_CLIENT_SECRET | From Discord OAuth2 | |
AUTH_COOKIE_SECRET | Strong secret | Used to sign session cookie. |
MONGO_URL | Production MongoDB | Required for users/roles/identities. |
Keep for production (Discord-first):
ADMIN_AUTH_USE_LOCAL=false(admin entrypoint = Discord OAuth).LOCAL_AUTH_ENABLED=falseunless 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/callbackhandler, afterensureUserAndIdentityand beforesendSession. - Logic: Call
resolvePermissions(tenantId, userId). Iflength === 0, call the same logic asensureLocalAdminAccessbut for thisuserId(seed default roles, get TenantAdmin by name, assign to user). Then callsendSessionas 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(defaultfalse). - When
true: keepADMIN_AUTH_USE_LOCAL=falseso 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 existingensureLocalAdminAccess(seed roles, assign TenantAdmin). - In production: set
LOCAL_AUTH_ENABLED=trueandADMIN_BOOTSTRAP_LOCAL_ENABLED=trueonly for initial setup; after first admin exists, setADMIN_BOOTSTRAP_LOCAL_ENABLED=false(and optionallyLOCAL_AUTH_ENABLED=false).
- Add
-
B) Reuse existing local entrypoint:
- For first-time setup, set
ADMIN_AUTH_USE_LOCAL=trueandLOCAL_AUTH_ENABLED=true. - Set
LOCAL_AUTH_DEFAULT_USERandLOCAL_AUTH_DEFAULT_PASSWORDto a strong bootstrap password. - Open
/admin→ redirects to local login → log in once →ensureLocalAdminAccessgives TenantAdmin. - Then set
ADMIN_AUTH_USE_LOCAL=false(and optionallyLOCAL_AUTH_ENABLED=false) and use Discord only.
- For first-time setup, set
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.
ensureUserAndIdentitycreates/links User and Identity (providerdiscord,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(providerlocal).ensureLocalAdminAccessassigns TenantAdmin when username matchesLOCAL_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
- Create or select Discord application; add redirect
https://<render-service>.onrender.com/api/auth/discord/callback. - Set Render env:
DISCORD_REDIRECT_URI,DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,AUTH_COOKIE_SECRET,MONGO_URL. - Keep
ADMIN_AUTH_USE_LOCAL=false(andLOCAL_AUTH_ENABLED=falsefor Discord-only). - Deploy; open
/admin→ redirect to Discord → log in. - After code change 4.1: first Discord user gets TenantAdmin and can access
/admin.
First-user fallback (username/password)
Option B (no new code):
- Set
ADMIN_AUTH_USE_LOCAL=trueandLOCAL_AUTH_ENABLED=truein Render. - Set
LOCAL_AUTH_DEFAULT_USERandLOCAL_AUTH_DEFAULT_PASSWORDto a strong password (e.g. random). - Deploy; open
/admin→ local login → log in once → TenantAdmin assigned. - Set
ADMIN_AUTH_USE_LOCAL=false(and optionallyLOCAL_AUTH_ENABLED=false) for normal Discord-only use.
Option A (if implemented):
- Set
ADMIN_BOOTSTRAP_LOCAL_ENABLED=trueandLOCAL_AUTH_ENABLED=true. - Use “First-time setup” link (or direct local start URL) with bootstrap credentials; after login, disable bootstrap flag.
6. Env Reference
| Variable | Production (Discord-first) | Bootstrap / first user |
|---|---|---|
DISCORD_REDIRECT_URI | https://...onrender.com/api/auth/discord/callback | Same |
DISCORD_CLIENT_ID | Set | Same |
DISCORD_CLIENT_SECRET | Set | Same |
ADMIN_AUTH_USE_LOCAL | false | true only for option B first login |
LOCAL_AUTH_ENABLED | false | true when using local bootstrap |
LOCAL_AUTH_DEFAULT_USER | — | Strong username for bootstrap |
LOCAL_AUTH_DEFAULT_PASSWORD | — | Strong password for bootstrap |
ADMIN_BOOTSTRAP_LOCAL_ENABLED | — | true only for option A bootstrap |
7. Risks and Mitigations
- Cookie domain / HTTPS: Render serves HTTPS; set
AUTH_COOKIE_SECRETand rely on existingsecureCookie: IS_PRODUCTIONso 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.