Jadu Auth (Backend)¶
Central authentication server for Scenarix applications. Handles user registration, login, session management, email verification (OTP), SMS OTP login, password reset, and RBAC (apps, roles, permissions).
Tech Stack¶
- Runtime: Node.js with TypeScript
- Framework: Express.js v5
- Database: MongoDB (native driver)
- Auth: Better Auth with JWT plugin (EdDSA), session (bearer) and admin plugins
- Validation: Zod
- API docs: OpenAPI 3.0 + Swagger UI
Quick Start¶
- Clone and install
cd jadu-auth-be
npm install
- Environment
Copy .env.example to .env and set at least:
PORT– server port (default8084)MONGODB_URI_JADU_AUTH– MongoDB connection stringBETTER_AUTH_SECRET– secret key (32+ characters); never commitBETTER_AUTH_URL– base URL of this server (e.g.http://localhost:8084)TRUSTED_ORIGINS– comma-separated allowed origins (e.g. frontend URL)
See .env.example for mail (SES/SMTP), SMS OTP (Twilio), email verification, and password reset options.
- Run
npm run dev
Server: http://localhost:8084
API docs: http://localhost:8084/api-docs
Main Endpoints¶
| Area | Examples |
|---|---|
| Auth | POST /api/auth/signup, POST /api/auth/login, POST /api/auth/logout, POST /api/auth/refresh, GET /api/auth/me |
| Password | POST /api/auth/forgot-password, POST /api/auth/reset-password, POST /api/auth/change-password |
| Email verification | POST /api/auth/send-verification-email, POST /api/auth/verify-email |
| Sessions | GET /api/auth/sessions, DELETE /api/auth/sessions/:sessionToken |
| JWKS | GET /.well-known/jwks (for JWT verification by other services) |
| Admin | GET/PUT /api/admin/users, GET /api/admin/users/:userId (admin role required) |
| RBAC | GET/POST/PUT/DELETE /api/admin/apps, roles, permissions, user role assignment (admin required) |
| Discord | POST /api/discord/interactions (Discord bot slash command + modal verification — see below) |
Full list and request/response shapes: Swagger UI at /api-docs or OpenAPI JSON at /api-docs.json.
Documentation¶
Backend docs live in docs/:
- docs/ARCHITECTURE.md — Folder map, request flow, stack and env
- docs/CONTRIBUTION.md — Adding routes, auth methods, tests; coverage rules
- docs/TESTS.md — How the integration test infra works
Code style and naming: CONVENTIONS.md.
Project Layout¶
src/index.ts– Express app, CORS, Better Auth mount, routes, Swaggersrc/lib/– auth (Better Auth config), db, jwt helpers, OpenAPI base, verification helpersrc/auth/– auth router, controller, service, validators, middleware, providers (email/password, session, email verification)src/admin/– admin router, controller, service, middleware, validatorssrc/rbac/– RBAC router, controller, service, types, schemas, validators, OpenAPIsrc/mail/– mail service, provider (SMTP/SES), template renderer, typessrc/models/– users, apps, OTP (MongoDB collections)src/shared/– constants, errors, helpers (response, error handler, UUID)
See docs/ARCHITECTURE.md for a detailed folder map and CONVENTIONS.md for naming and request flow.
Scripts¶
| Command | Description |
|---|---|
npm run dev |
Start with nodemon (ts-node) |
npm run build |
Compile TypeScript to dist/ |
npm start |
Run dist/index.js |
npm run lint |
ESLint |
npm run setup |
Install Lefthook git hooks (pre-push only when be/ is pushed: lint, build, integration tests with 98% coverage). Run from repo root with npm run setup or from be/; config lives at repo root lefthook.yml. |
npm run discord:register-commands |
One-time per guild: registers the /verify slash command (needs DISCORD_CLIENT_ID + DISCORD_BOT_TOKEN + DISCORD_GUILD_ID). |
npm run discord:post-welcome |
Posts (or edits, if DISCORD_START_HERE_MESSAGE_ID is set) the welcome message + "Verify" button into #start-here. |
Discord "Verified Creator" role (in-Discord bot flow)¶
Members of the Studio Jadu Discord get a role automatically once they link their Jadu account —
entirely inside Discord, no browser, no Discord OAuth. Entry points: the /verify slash command,
or the "Verify your account" button in #start-here (posted by discord:post-welcome) — both open
the same email modal.
/verify or #start-here button
→ email modal (Discord-native popup)
→ submit → defer, then real Jadu OTP sent (EmailOtpService.loginInit, so the same
per-email 60s resend cooldown as the web login applies) → ephemeral message + "Enter code" button
(a button, not a second modal directly — Discord doesn't support opening a modal
as the reply to a modal submission, so a button click carries the state instead)
→ click button → code modal
→ submit → defer, then check the OTP (OtpService, no session or tokens are created —
this proves email ownership, it is not a login) → link accounts
(discordLinks.model.ts) → RBAC auto-grant → bot assigns the role directly
(PUT /guilds/{g}/members/{u}/roles/{r}) → ephemeral "✅ Verified!" message
The Discord identity comes straight from the interaction's signature (member.user), never from
OAuth — Discord cryptographically signs every interaction with the real invoking user's ID, so
there's nothing to spoof and no refresh token to store.
Implementation: src/discord/bot/; Discord HTTP client: src/thirdPartyServices/discord/; auth
app config (discord_verify, auto-grants USER on first use): src/authApps/discordVerify.config.ts.
Env vars: see the Discord block in .env.example.
Why a bot instead of Discord's Linked Roles feature? Linked Roles requires the user to manually claim the role afterward (Discord's own docs: "this ensures members actually want the role, rather than auto-assigning them" — no API/bot workaround exists) and is poorly discovered (buried in a server-name dropdown menu). A bot with Manage Roles assigns an ordinary role directly — zero claim step — which is how virtually every commercial "verified member" integration works (Patreon, Collab.Land, Ko-fi, Whop, Guild.xyz).
Per-environment Discord app setup (Developer Portal): OAuth2 scopes aren't needed at all for this
flow. Copy the Application ID (DISCORD_CLIENT_ID) and Bot Token (DISCORD_BOT_TOKEN)
from the app, and the Public Key (DISCORD_PUBLIC_KEY) from General Information. Invite the
bot with OAuth2 → URL Generator → scope bot + permission Manage Roles (this is what makes it an
actual guild member — enabling only Linked Roles/slash commands does not). In the server: create a
plain (non-Linked) role for the bot to assign, position the bot's own role above it in Server
Settings → Roles (Discord's permission hierarchy rule — the grant call 403s otherwise), then run
discord:register-commands and discord:post-welcome once each.
Security Notes¶
- Use a strong
BETTER_AUTH_SECRET(32+ chars) and keep it out of version control. - In production, set
TRUSTED_ORIGINSto your real frontend/backend URLs. - Session cookie is HttpOnly, and
securein production; access token is short-lived (15 min). - Forgot-password and send-verification-email responses do not reveal whether an email exists.
- SMS OTP login-init responses do not reveal whether a phone number exists.
- Exception, by design: the Discord bot flow tells the user when no Jadu account matches the email
they typed. Without it a typo looks identical to a delayed email and the user is stuck. The reply
is ephemeral (visible only to them), and it reveals nothing the public email-OTP
login-initendpoint does not already reveal with its "No account found" response.
A more detailed code and security review is in REVIEW.md.
License¶
ISC