API

Sessions and auth

Read the session on either side, and add your own fields to it.

On the server

Every procedure gets the session as ctx.session. On protectedProcedure and anything built on it, it's never null:

me: protectedProcedure.query(({ ctx }) => ({
  id: ctx.session.user.id,
  role: ctx.session.user.role,
})),

Outside tRPC, in a plain Fastify route, read it with getActiveSession from backend/src/auth.ts.

On the client

The web app's auth client is in web-app/src/AuthHelpers/auth-client.ts:

import { authClient } from "./AuthHelpers/auth-client";

const { data: session, isPending } = authClient.useSession();

Sign-in, sign-up, sign-out, and password reset are methods on the same client — authClient.signIn.email, authClient.signOut, and so on. See the Better Auth docs.

What's on the session

FieldWhat it is
user.roleThe global role name
user.viewModeLight, dark, or follow the system
user.activeFalse when an admin has deactivated the account
enabledFeaturesEvery feature the role has, enabled and granted
hasAcceptedTermsAndConditionstrue, false, or null when no terms are active
avatarUrlA signed URL for the uploaded avatar, if there is one

Adding a field

There are two kinds.

A column on the user — add it to user.additionalFields in auth.ts, with input: false so users can't set it themselves. Then regenerate the auth schema and write a migration:

yarn workspace backend db:auth:generate
yarn workspace backend db:generate

A computed value — add it to the customSession plugin in auth.ts. It runs on every session read, so keep it fast.

Both reach the client's types automatically through inferAdditionalFields and customSessionClient.

Sign-in methods

Email and Google can each be switched on or off in Admin → Site Settings. Only email is on in a fresh database. The login page asks authProtocols.list which ones to show.

Who becomes what

The first account ever becomes owner. Every later account starts as demo. To change that, edit nextUserRole in auth.ts — and update PROTECTED_ROLES in trpc/routers/roles.ts to match.