packages/auth holds the better-auth instance and everything it needs to tie an auth user to a studio. The behaviour is described on Authentication. This page is the map of the package.

Public exports

src/index.ts exports everything from auth.ts plus the config: core-api imports auth for AppContext and the in-process session verifier. @repo/api imports it for the handler and for getSession in procedures.

Files

config.ts

Only some flags are read on the server: sessionCookieMaxAge sets the session lifetime and enableSignup controls the invitation only plugin. The rest are read by the web app, which has the same config in its own repo.

lib/phone.ts

Pure functions, with tests beside them. core-api has its own apps/core-api/src/lib/phone.ts with toMsisdn, normalizePhone and phoneKey for domain rows. The two files implement the same toMsisdn rules separately. A change to number handling has to be made in both.

lib/phone-otp.ts

lib/staff-account.ts

lib/claim-staff-seats.ts

claimStaffSeats(userId, email) runs in the session create hook. It links active coach rows that match the user or the email, makes sure a member row exists with the right role, and returns the organization the new session should open. Every failure is logged and swallowed. A broken claim must not block sign-in.

lib/provision-coach.ts

provisionCoach({ studioId, externalUserId, name, email, role }) signs and sends POST /v1/internal/coaches to CORE_API_URL. See the warning about its studioId on Authentication.

lib/organization.ts

updateSeatsInOrganizationSubscription(organizationId) looks for a purchase on a seat based price (seatBased: true, the legacy teamSeat add-on). If the organization has one, it sets its seats to the active coach count minus the 2 included seats, never below 1. For every other studio it returns without doing anything.

lib/studio-lifecycle.ts

These write domain tables directly through db. They used to be signed HTTP calls from the API to itself. Doing the write in process removed every way that call could silently fail.

lib/deletion-hooks.ts

beforeOrganizationDelete(organizationId) and beforeUserDelete(userId). Each cancels the relevant billed subscriptions first, then quiesces the domain side.

lib/helper.ts

isOrganizationAdmin(organization, user) is true when the user’s member role in the organization is owner or admin, or the user is a platform admin.

Plugins

Tests

The package has tests beside the code: auth-deletion-hooks.test.ts, lib/claim-staff-seats.test.ts, lib/deletion-hooks.test.ts, lib/organization.test.ts, lib/phone.test.ts, lib/staff-account.test.ts, lib/studio-lifecycle.test.ts.

Changing the auth configuration

  • A new plugin or option goes in auth.ts. If it adds tables or columns, add the models to packages/database/prisma/schema.prisma under @@schema("auth") and write a migration in packages/db.
  • A new field on the user goes in user.additionalFields and in the User model.
  • Rebuild with pnpm --filter @repo/auth build. core-api loads the package from dist.
  • The web app has its own better-auth client configuration. A server side plugin usually needs its client counterpart there.