7.9 KiB
Add @suaveplan/react-auth — core (Wave 1)
Package: @suaveplan/react-auth
Status: PROPOSED
Type: new-package
- Spec delta: ./specs/react-auth/spec.md
Why
Genesis needs a React Better-Auth integration so downstream apps can authenticate users, gate routes, and check permissions. The upstream package at ../suaveplan/packages/react/react-auth/ ships a typed AuthClient wrapper around better-auth/client, an AuthProvider context, AuthGuard route gate, useAuthGuard/usePermission hooks, a betterAuthAdapter bridge, locale packs for en-GB/cy-GB/zh-CN, plus full page and form surfaces. Wave 1 ships the foundational core — adapter, client, context, hooks, guard, translations data, types — so that downstream packages (the parallel react-auth-whitelabel and consumer apps) can compile and authenticate today. Forms (Wave 2) and pages (Wave 3) layer on top of this core without changing it.
What changes
- ADDED:
AuthClient+createAuthClient(config)— typed wrapper aroundbetter-auth/clientwithx-site-domainheader propagation. - ADDED:
AuthProvider+useAuth— React context wrappingAuthClientwithsignIn/signUp/signOut/updateProfile/changePassword/forgotPassword/resetPassword/verifyEmail/resendVerificationEmail/deleteAccount/refreshSessionactions and{ user, session, isLoading, isAuthenticated, error }state. - ADDED:
AuthGuardcomponent — declarativerequireAuth/requireGuest/requireVerified/allowedRolesgate withloadingComponent+fallbackslots. - ADDED:
useAuthGuard(options)hook — imperativereact-router-domredirect counterpart toAuthGuard. - ADDED:
usePermission()hook — bitwise permission check (16 flags) againstuser.roles. - ADDED:
transformSessionResponse/transformAuthResponse/transformGenericResponse/transformUpdateProfileResponse/isBetterAuthError/hasBetterAuthData— better-auth response adapter functions. - ADDED: Locale packs
enGb/cyGb/zhCnand aggregateauthTranslations(Wave 2 forms consume these). - ADDED:
User/Session/Account/LoginCredentials/RegisterData/ResetPasswordData/ResetPasswordCredentials/UpdateProfileData/ChangePasswordData/AuthResponse/AuthError(auth.types.ts) andSite/UserSettings/BankAccount/Friend/Group/Post/Message/Notification/Event/Role/DeviceToken/PushNotification(crud.types.ts). - ADDED:
REACT_AUTH_CODESregistry (10 stable codes) +ReactAuthErrorbase class + 10 typed subclasses. - ADDED: OTel instruments under meter
suaveplan.react-auth— counters for sign-in/sign-up/sign-out/guard-allow/guard-deny, histogram for session-fetch duration.
Out of scope
- Form components (
LoginForm,RegisterForm,ForgotPasswordForm,ResetPasswordForm,EmailVerifyForm) — deferred to Wave 2. - Page components (
Login,Register,ForgotPassword,ResetPassword,EmailVerify,Profile) andPermissionGate— deferred to Wave 3.
Excluded from this migration
- Form components (
login-form,register-form,forgot-password-form,reset-password-form,email-verify-form) — Deferred to Wave 2 — scope-bounded migration to keep ship velocity at 100/100/100/100 quality. - Page components (
login,register,forgot-password,reset-password,email-verify,profile) andPermissionGate— Deferred to Wave 3 — scope-bounded migration to keep ship velocity at 100/100/100/100 quality.
Source mapping
This change derives from ../suaveplan/enterprise/react-auth.md and ../suaveplan/packages/react/react-auth/src/.
| Requirement | Source lines | Notes |
|---|---|---|
AuthClient wraps better-auth/client |
client/auth-client.ts:1-237 |
Reworded: @ts-expect-error on forgetPassword replaced with conditional dispatch through an optional-method lookup, removing the suppression. |
AuthProvider context + 11 actions |
context/auth-context.tsx:1-435 |
Verbatim semantics; replaced loose (error as Error) casts with instanceof Error ? error : new Error(String(error)) to satisfy useThrowOnlyError. |
AuthGuard declarative gate |
components/auth-guard/auth-guard.tsx:1-69 |
Verbatim. |
useAuthGuard imperative gate |
hooks/use-auth-guard.ts:1-86 |
Verbatim. |
usePermission bitwise hook |
hooks/use-permission.ts:1-88 |
Verbatim. |
betterAuthAdapter response transforms |
lib/better-auth-adapter.ts:1-215 |
Verbatim; any payloads narrowed via unknown + structural type-guards. |
Locale packs (en-GB, cy-GB, zh-CN) |
translations/{en-gb,cy-gb,zh-cn}.ts |
Verbatim string tables. |
| Auth + CRUD types | types/{auth.types,crud.types}.ts |
Verbatim shapes. |
Requirements with no source line
| Requirement | Justification |
|---|---|
REACT_AUTH_CODES error-code registry + ReactAuthError subclasses |
Genesis-wide invariant (@suaveplan/error defineCodes); upstream threw plain Error. Audit enterprise/react-auth.md §3 flagged loss of programmatic handling and OTel tagging. |
OTel instruments under suaveplan.react-auth |
Genesis-wide invariant (every package emits structured metrics). |
Zod validation at createAuthClient config + useAuthGuard options + AuthGuardProps boundary |
Genesis rule §0a — Zod boundary on every public option bundle. |
FetchLike injection seam at AuthClient construction |
Required by the testing rule "no mocks; real services or injectable boundaries". |
Verification approach
src/client/auth-client.test.ts— coversAuthClientconstruction,siteDomainheader propagation, every wrapper method's call shape, session-list date normalisation.src/lib/better-auth-adapter.test.ts— covers all 6 transform functions over success/error/empty payloads.src/context/auth-context.test.tsx— coversAuthProviderinitial-session, all 11 actions, error state,skipInitialCheck,onAuthStateChangenotification, unmount cleanup,useAuthoutside-provider throw.src/hooks/use-auth-guard.test.tsx— covers all 4 redirect paths (auth, guest, verified, role) +canAccessmatrix + loading bail-out.src/hooks/use-permission.test.tsx— covershasPermission/hasAnyPermission/hasAllPermissions/getPermissionNameswith string & numeric inputs, unauthenticated bail-outs.src/components/auth-guard/auth-guard.test.tsx— covers loading state, every grant/deny branch, customloadingComponent+fallbackslots, multi-child rendering.src/components/auth-provider/auth-provider.test.tsx— covers the re-export surface compiles to the canonicalAuthProviderand type re-exports.src/types/auth.types.test.ts+src/types/crud.types.test.ts—expectTypeOfshape pins for every exported interface.src/translations/translations.test.ts— locale-pack key-parity acrossen-gb/cy-gb/zh-cn, default export shape.src/error-codes/error-codes.test.ts— every code thrown by at least one production path; error name + code field round-trip.src/observability/react-auth-metrics.test.ts— counters + histogram emit through a real in-memoryMeterProvider.
Migration impact
../suaveplan/packages/react/react-auth/src/components/auth-provider/auth-provider.types.tsimportsPartialResources+SupportedLanguagefrom@suaveplan/react-translate. Wave 1 does not consume these types at runtime (the legacyAuthProviderProps.translationConfigis a Wave-2 form concern); they are re-declared as localTranslationConfig { defaultLanguage?: string; resources?: Record<string, unknown> }so this Wave-1 surface compiles without an@suaveplan/react-i18ndependency. Wave 2 will tighten the type when it wiresuseTranslation.
Impact
Affected dependents
No dependents yet — this is a new package landing on a clean genesis branch. Verified by bunx turbo ls --filter=...@suaveplan/react-auth (empty downstream list).