PackagesauthMenu

@intelligo-dev/auth

Multi-tenant authentication on Better-Auth: sign-up, workspaces, invitations and role-based access control.

Install

Terminal
pnpm add @intelligo-dev/auth better-auth drizzle-orm react zod

better-auth, drizzle-orm, react and zod are peers: the guards read the Better-Auth instance and the database client the application already has, and the verification and reset emails render with React.

What it owns

Sessions and accounts, organizations as workspaces, membership roles (owner / admin / member), invitations, platform-admin impersonation, and the guards every transport starts with: requireAuth, requireWorkspace, requireRole, requirePlatformAdmin.

The team, workspace, profile and onboarding services live here too, behind ports the composition root binds — auth never imports billing.

Use

TypeScript
import { requireWorkspace } from "@intelligo-dev/auth";

const { user, workspace, membership } = await requireWorkspace();

A guard that refuses throws an AuthGuardError; isAuthGuardError(error) narrows it and error.code picks the status: unauthenticated (no session, 401), no_workspace (signed in, member of no workspace) or forbidden (lacking the role, 403).

impersonateUser checks that the caller is a platform admin and that the target is not one before it swaps the session. Products call startImpersonation from @intelligo-dev/admin, which adds the audit event and the mandatory reason.

hasSessionCookie(request) from @intelligo-dev/auth/edge is for an optional optimistic redirect in a consumer’s proxy.ts. It reads cookie presence only and is never authorization; the scaffold does not use it.

Platform admin is a row (users.role), not an environment variable. The allowlist in PLATFORM_ADMIN_EMAILS is promoted into that column on first use, so the plugin and the guard cannot disagree. The allowlist counts a verified address only; on a machine that verifies none, intelligo admin grant <email> writes the column directly, and intelligo admin revoke <email> takes it away.

Workspaces and memberships change through the team and workspace services, which apply the plan’s limits and the deletion hook around Better-Auth’s organization plugin; the plugin’s own mutating endpoints answer 404 over HTTP (SERVICE_ONLY_ORGANIZATION_PATHS). An invitation can carry a product’s own fields (inviteMember({ email, role, productFields }), a few short strings — what the member is to the workspace, say); they are copied onto the membership when it is accepted and read back with memberProductFields({ workspaceId, userId }). Behind proxies, AUTH_IP_HEADERS (headers to read the client address from, in order) and AUTH_TRUSTED_PROXIES (CIDRs) let the auth rate limiter tell clients apart.

Deleting an account (createProfileService().deleteAccount) schedules it: it is refused while the user is the only owner of a workspace other people belong to, and the workspaces they own alone are deleted with it. Until the grace period ends — ACCOUNT_DELETION_GRACE_DAYS, 30 by default — signing in leads to a restore screen (getPendingDeletion, restoreAccount); every other guard treats that session as signed out. After it, purgeDeletedAccounts, called from the maintenance route, deletes the account and erases the user from the memory audit.

Entry points

  • @intelligo-dev/auth
  • @intelligo-dev/auth/client
  • @intelligo-dev/auth/edge

npm · source