Auth & Accounts
apps/api/src/router/authRoutes.ts · the only router that is public by default.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /authenticate | — | Log in, returns { user, token } |
POST | /create-account | — | Register, returns { user, token } |
POST | /logout | — | Log out |
POST | /check-username | — | Is a username taken? |
POST | /check-username-offensive | — | Does a username contain profanity? |
POST | /forgot-password | — | Stub |
POST | /reset-password | — | Stub |
POST | /verify-identity | ✔ | Submit identity verification |
PUT | /auth/change-password | ✔ | Change password (in userRoutes) |
POST | /auth/logout-all | ✔ | Revoke every session (in userRoutes) |
POST /authenticate
{ "email": "user@example.com", "password": "…" }{
"user": { "id": "clx…", "email": "…", "username": "…", "onboarded": true, "userType": "user" },
"token": "eyJhbGciOiJIUzI1NiIs…"
}Also creates an AuthSession audit record capturing device name, IP, and user agent. Invalid credentials return an error from NotFoundError — the same message for "no such user" and "wrong password", which is the right choice for enumeration resistance.
Store the token and send it as Authorization: Bearer <token> on every subsequent request. It is valid for 7 days; there is no refresh token, so the client re-authenticates when it expires.
POST /create-account
{ "first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.com", "password": "…" }Registration does three things beyond creating the user:
- Hashes the password with bcrypt.
- Creates a
LiveStreamrecord with Mux credentials — every user is stream-ready from day one. - Creates an
AuthSession.
The response matches /authenticate. Note that username is not set at registration — it is chosen during onboarding, so User.username is nullable.
Username checks
POST /check-username
{ "username": "gamer123" }
→ { "exists": false }POST /check-username-offensive runs the trie-based filter in apps/api/src/lib/offensive-words-validator/. Call both before letting a user commit to a name.
PUT /auth/change-password
Requires authentication. Changing a password should also increment tokenVersion so other devices are logged out — verify that behaviour before relying on it.
POST /auth/logout-all
The real revocation mechanism:
prisma.user.update({ where: { id: userId }, data: { tokenVersion: { increment: 1 } } });
prisma.authSession.updateMany({ where: { userId }, data: { isActive: false } });Incrementing tokenVersion invalidates every JWT ever issued to that user, because the auth middleware compares the token's version against the user's. See Authentication.
POST /logout
Client-side logout. Since JWTs are stateless, this cannot invalidate the token — the client discards it. Use /auth/logout-all when you actually need revocation.
POST /verify-identity
Part of creator onboarding, alongside POST /verification-asset for uploading identity documents. The mobile flow is id-capture → selfie → review-pending (apps/mobile/app/(onboarding)/).
Password reset is not implemented
export async function forgotPassword(email: string) {}
export async function resetPassword(token: string, password: string) {}Both routes exist and both service functions are empty. RESET_SECRET is configured in anticipation. Implementing this needs an email provider — packages/transactional holds templates but is not wired to one. Planned
Related
- Authentication architecture — token format, revocation, socket auth
- Users & Profiles — sessions, login history, account deletion
