@opensaas/stack-auth 0.39.2 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +151 -0
  3. package/CLAUDE.md +140 -22
  4. package/dist/client/index.d.ts +0 -16
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/config/adopt-better-auth-tables.d.ts +4 -49
  7. package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
  8. package/dist/config/adopt-better-auth-tables.js +4 -51
  9. package/dist/config/adopt-better-auth-tables.js.map +1 -1
  10. package/dist/config/derive-auth-lists.d.ts +42 -37
  11. package/dist/config/derive-auth-lists.d.ts.map +1 -1
  12. package/dist/config/derive-auth-lists.js +579 -223
  13. package/dist/config/derive-auth-lists.js.map +1 -1
  14. package/dist/config/index.d.ts +0 -3
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +4 -34
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/plugin.d.ts.map +1 -1
  19. package/dist/config/plugin.js +20 -90
  20. package/dist/config/plugin.js.map +1 -1
  21. package/dist/config/types.d.ts +89 -67
  22. package/dist/config/types.d.ts.map +1 -1
  23. package/dist/index.d.ts +1 -9
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/lists/index.d.ts +9 -5
  28. package/dist/lists/index.d.ts.map +1 -1
  29. package/dist/lists/index.js +9 -6
  30. package/dist/lists/index.js.map +1 -1
  31. package/dist/mcp/better-auth.d.ts +6 -19
  32. package/dist/mcp/better-auth.d.ts.map +1 -1
  33. package/dist/mcp/better-auth.js +6 -20
  34. package/dist/mcp/better-auth.js.map +1 -1
  35. package/dist/plugins/index.d.ts +4 -4
  36. package/dist/plugins/index.d.ts.map +1 -1
  37. package/dist/plugins/index.js +4 -4
  38. package/dist/plugins/index.js.map +1 -1
  39. package/dist/runtime/types.d.ts +2 -9
  40. package/dist/runtime/types.d.ts.map +1 -1
  41. package/dist/runtime/types.js +0 -4
  42. package/dist/runtime/types.js.map +1 -1
  43. package/dist/server/index.d.ts +5 -12
  44. package/dist/server/index.d.ts.map +1 -1
  45. package/dist/server/index.js +14 -62
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/ui/components/ForgotPasswordForm.d.ts +0 -3
  48. package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -1
  49. package/dist/ui/components/ForgotPasswordForm.js +0 -3
  50. package/dist/ui/components/ForgotPasswordForm.js.map +1 -1
  51. package/dist/ui/components/ResetPasswordForm.d.ts +0 -3
  52. package/dist/ui/components/ResetPasswordForm.d.ts.map +1 -1
  53. package/dist/ui/components/ResetPasswordForm.js +0 -5
  54. package/dist/ui/components/ResetPasswordForm.js.map +1 -1
  55. package/dist/ui/components/SignInForm.d.ts +0 -3
  56. package/dist/ui/components/SignInForm.d.ts.map +1 -1
  57. package/dist/ui/components/SignInForm.js +0 -4
  58. package/dist/ui/components/SignInForm.js.map +1 -1
  59. package/dist/ui/components/SignUpForm.d.ts +0 -3
  60. package/dist/ui/components/SignUpForm.d.ts.map +1 -1
  61. package/dist/ui/components/SignUpForm.js +0 -5
  62. package/dist/ui/components/SignUpForm.js.map +1 -1
  63. package/dist/ui/lib/clean-error-message.d.ts +3 -6
  64. package/dist/ui/lib/clean-error-message.d.ts.map +1 -1
  65. package/dist/ui/lib/clean-error-message.js +3 -6
  66. package/dist/ui/lib/clean-error-message.js.map +1 -1
  67. package/dist/ui/types.d.ts +2 -15
  68. package/dist/ui/types.d.ts.map +1 -1
  69. package/dist/ui/types.js +2 -7
  70. package/dist/ui/types.js.map +1 -1
  71. package/package.json +12 -5
  72. package/src/client/index.ts +0 -17
  73. package/src/config/adopt-better-auth-tables.ts +4 -51
  74. package/src/config/derive-auth-lists.ts +685 -252
  75. package/src/config/index.ts +4 -34
  76. package/src/config/plugin.ts +21 -91
  77. package/src/config/types.ts +90 -67
  78. package/src/index.ts +3 -14
  79. package/src/lists/index.ts +17 -5
  80. package/src/mcp/better-auth.ts +6 -25
  81. package/src/plugins/index.ts +4 -5
  82. package/src/runtime/types.ts +2 -10
  83. package/src/server/index.ts +17 -70
  84. package/src/ui/components/ForgotPasswordForm.tsx +0 -3
  85. package/src/ui/components/ResetPasswordForm.tsx +0 -5
  86. package/src/ui/components/SignInForm.tsx +0 -4
  87. package/src/ui/components/SignUpForm.tsx +0 -5
  88. package/src/ui/lib/clean-error-message.ts +3 -6
  89. package/src/ui/types.ts +2 -15
  90. package/tests/auth-lists-drift.test.ts +480 -0
  91. package/tests/config.test.ts +3 -0
  92. package/tests/credential-field-read-deny-e2e.test.ts +295 -0
  93. package/tests/derive-auth-lists.test.ts +263 -6
  94. package/tests/generated-fk-shape.test.ts +432 -9
  95. package/tests/mcp-oauth-cascade-e2e.test.ts +267 -0
  96. package/tests/plugin-derived-keys.test.ts +39 -0
  97. package/tests/plugin-table-derivation.test.ts +270 -0
  98. package/tsconfig.tsbuildinfo +1 -1
  99. package/dist/server/schema-converter.d.ts +0 -81
  100. package/dist/server/schema-converter.d.ts.map +0 -1
  101. package/dist/server/schema-converter.js +0 -157
  102. package/dist/server/schema-converter.js.map +0 -1
  103. package/src/server/schema-converter.ts +0 -243
  104. package/tests/schema-converter.test.ts +0 -399
@@ -1,14 +1,5 @@
1
- /**
2
- * Better Auth integration for MCP OAuth authentication
3
- * Provides session handling and authentication utilities
4
- */
5
-
6
1
  import type { McpSession, McpSessionProvider } from '@opensaas/stack-core/mcp'
7
2
 
8
- /**
9
- * Better Auth instance type (flexible)
10
- * Uses minimal typing to avoid tight coupling with better-auth package
11
- */
12
3
  export type BetterAuthInstance = {
13
4
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Better Auth API types vary by plugins, must use any
14
5
  api: any
@@ -65,13 +56,11 @@ export function withMcpAuth(
65
56
  handler: (req: Request, session: McpSession) => Promise<Response> | Response,
66
57
  ): (req: Request) => Promise<Response> {
67
58
  return async (req: Request) => {
68
- // Extract MCP session from Better Auth
69
59
  const session = await auth.api.getMcpSession({
70
60
  headers: req.headers,
71
61
  })
72
62
 
73
63
  if (!session) {
74
- // Return 401 with WWW-Authenticate header for OAuth clients
75
64
  return new Response(null, {
76
65
  status: 401,
77
66
  headers: {
@@ -80,7 +69,6 @@ export function withMcpAuth(
80
69
  })
81
70
  }
82
71
 
83
- // Call handler with authenticated session
84
72
  return handler(req, session)
85
73
  }
86
74
  }
@@ -118,25 +106,20 @@ export function hasScopes(session: McpSession, requiredScopes: string[]): boolea
118
106
  return requiredScopes.every((scope) => session.scopes!.includes(scope))
119
107
  }
120
108
 
121
- /**
122
- * Check if MCP session is expired
123
- */
124
109
  export function isSessionExpired(session: McpSession): boolean {
125
110
  if (!session.expiresAt) return false
126
111
  return new Date() > session.expiresAt
127
112
  }
128
113
 
129
114
  /**
130
- * Create OAuth discovery metadata handler
131
- * Exposes OAuth authorization server metadata for MCP clients
115
+ * Exposes OAuth authorization server metadata for MCP clients.
132
116
  *
133
- * This should be placed at `/.well-known/oauth-authorization-server/route.ts`
134
- * Better Auth already handles `/api/auth/.well-known/oauth-authorization-server`
135
- * but some clients may fail to parse WWW-Authenticate headers
117
+ * Place at `/.well-known/oauth-authorization-server/route.ts`. Better Auth
118
+ * already handles `/api/auth/.well-known/oauth-authorization-server`, but
119
+ * some clients may fail to parse the `WWW-Authenticate` header.
136
120
  */
137
121
  export function createOAuthDiscoveryHandler(_auth: BetterAuthInstance) {
138
122
  return async (req: Request) => {
139
- // Delegate to Better Auth's built-in handler
140
123
  const authPath = '/api/auth/.well-known/oauth-authorization-server'
141
124
  const authUrl = new URL(authPath, req.url)
142
125
 
@@ -147,14 +130,12 @@ export function createOAuthDiscoveryHandler(_auth: BetterAuthInstance) {
147
130
  }
148
131
 
149
132
  /**
150
- * Create OAuth protected resource metadata handler
151
- * Exposes OAuth protected resource metadata for MCP clients
133
+ * Exposes OAuth protected resource metadata for MCP clients.
152
134
  *
153
- * This should be placed at `/.well-known/oauth-protected-resource/route.ts`
135
+ * Place at `/.well-known/oauth-protected-resource/route.ts`.
154
136
  */
155
137
  export function createOAuthProtectedResourceHandler(_auth: BetterAuthInstance) {
156
138
  return async (req: Request) => {
157
- // Delegate to Better Auth's built-in handler
158
139
  const authPath = '/api/auth/.well-known/oauth-protected-resource'
159
140
  const authUrl = new URL(authPath, req.url)
160
141
 
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Re-export Better Auth plugins for convenience
3
- * This allows users to import plugins from @opensaas/stack-auth/plugins
4
- * instead of better-auth/plugins
2
+ * better-auth 1.7 split the MCP plugin out of `better-auth/plugins` into its
3
+ * own `@better-auth/mcp` package (an optional peer of this package) — see the
4
+ * 1.7.1 upgrade note in the auth package's CHANGELOG.
5
5
  */
6
-
7
- export { mcp } from 'better-auth/plugins'
6
+ export { mcp } from '@better-auth/mcp'
@@ -1,25 +1,17 @@
1
1
  /**
2
- * Type definitions for auth plugin runtime services
3
- * These types are used for type-safe access to context.plugins.auth
4
- */
5
-
6
- /**
7
- * Runtime services provided by the auth plugin
8
- * Available via context.plugins.auth
2
+ * Runtime services provided by the auth plugin, available via
3
+ * `context.plugins.auth`.
9
4
  */
10
5
  export interface AuthRuntimeServices {
11
6
  /**
12
- * Get user by ID.
13
7
  * Resolves through the plugin runtime's `sudo` helper, so the result does
14
8
  * not depend on the application's User list access policy (ADR-0013).
15
9
  *
16
- * @param userId - The ID of the user to fetch
17
10
  * @returns User object or null if not found
18
11
  */
19
12
  getUser: (userId: string) => Promise<unknown>
20
13
 
21
14
  /**
22
- * Get current user from session.
23
15
  * Extracts userId from session and fetches user data through the plugin
24
16
  * runtime's `sudo` helper — see {@link getUser}.
25
17
  *
@@ -7,13 +7,9 @@ import type { DatabaseConfig } from '@opensaas/stack-core/internal'
7
7
  import type { NormalizedAuthConfig, NormalizedAuthModelConfig } from '../config/types.js'
8
8
 
9
9
  /**
10
- * The `BetterAuthOptions` shape produced when an app's own plugin tuple is
11
- * passed to `buildBetterAuthOptions()`/`createAuth()` — the tuple plus the
12
- * `nextCookies()` plugin the stack always appends last. Carrying the literal
13
- * tuple type (rather than the widened `BetterAuthPlugin[]`) is what lets
14
- * `betterAuth()` re-infer plugin endpoints (e.g. `emailOTP()`'s
15
- * `api.signInEmailOTP`) and a `customSession()` plugin's replaced session
16
- * shape from the resulting options object.
10
+ * `BetterAuthOptions['plugins']` narrowed to the app's literal tuple plus the
11
+ * appended `nextCookies()` — see {@link buildBetterAuthOptions}'s tuple-typing
12
+ * doc for why this matters.
17
13
  */
18
14
  type ResolvedBetterAuthOptions<TPlugins extends readonly BetterAuthPlugin[]> = Omit<
19
15
  BetterAuthOptions,
@@ -53,9 +49,6 @@ function assertPluginTupleMatchesResolved(
53
49
  }
54
50
  }
55
51
 
56
- /**
57
- * Get better-auth database configuration from OpenSaas config
58
- */
59
52
  function getDatabaseConfig(
60
53
  dbConfig: DatabaseConfig,
61
54
  context: AccessContext,
@@ -66,10 +59,8 @@ function getDatabaseConfig(
66
59
  }
67
60
 
68
61
  /**
69
- * Translate a normalized OpenSaaS auth model config into the better-auth
70
- * per-model options (`modelName` + `fields` column map). Returns `undefined`
71
- * when there is nothing to override so the running auth instance keeps
72
- * better-auth's own defaults untouched.
62
+ * Returns `undefined` when there is nothing to override, so the running auth
63
+ * instance keeps better-auth's own defaults untouched.
73
64
  */
74
65
  function toBetterAuthModelOptions(
75
66
  model: NormalizedAuthModelConfig,
@@ -156,13 +147,6 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
156
147
  )
157
148
  }
158
149
 
159
- /**
160
- * Deep-merge `overrides` onto `base`, recursing into plain-object values so a
161
- * nested addition (one database hook, one session sub-option) merges
162
- * alongside sibling keys the stack already set there rather than replacing
163
- * the whole branch. Arrays and any other value type replace outright.
164
- * `overrides` wins on every key collision.
165
- */
166
150
  function mergeBetterAuthOptions(
167
151
  base: Record<string, unknown>,
168
152
  overrides: Record<string, unknown>,
@@ -239,7 +223,6 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
239
223
  const resolvedConfig = await Promise.resolve(opensaasConfig)
240
224
  const resolvedContext = await Promise.resolve(context)
241
225
 
242
- // Extract auth config from plugin data
243
226
  const authConfig = resolvedConfig._pluginData?.auth as NormalizedAuthConfig | undefined
244
227
 
245
228
  if (!authConfig) {
@@ -280,13 +263,9 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
280
263
  assertPluginTupleMatchesResolved(plugins, resolvedPlugins)
281
264
  }
282
265
 
283
- // Build better-auth configuration
284
266
  const betterAuthConfig: BetterAuthOptions = {
285
267
  database: getDatabaseConfig(resolvedConfig.db, resolvedContext),
286
268
 
287
- // Mirror the per-model config (modelName + field column maps) back to
288
- // better-auth so the running auth instance reads/writes the same
289
- // tables/columns the OpenSaaS Auth lists were derived from.
290
269
  user: toBetterAuthModelOptions(authConfig.models.user),
291
270
  session: {
292
271
  ...toBetterAuthModelOptions(authConfig.models.session),
@@ -301,7 +280,6 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
301
280
  account: toBetterAuthModelOptions(authConfig.models.account),
302
281
  verification: toBetterAuthModelOptions(authConfig.models.verification),
303
282
 
304
- // Enable email and password if configured
305
283
  emailAndPassword: authConfig.emailAndPassword.enabled
306
284
  ? {
307
285
  enabled: true,
@@ -316,8 +294,8 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
316
294
  }
317
295
  : undefined,
318
296
 
319
- // Email verification (independent of emailAndPassword — also covers
320
- // e.g. a social-provider account whose email isn't yet verified)
297
+ // Independent of `emailAndPassword` — also covers e.g. a social-provider
298
+ // account whose email isn't yet verified.
321
299
  emailVerification: authConfig.emailVerification.enabled
322
300
  ? {
323
301
  sendVerificationEmail: authConfig.emailVerification.sendVerificationEmail,
@@ -326,10 +304,8 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
326
304
  }
327
305
  : undefined,
328
306
 
329
- // Trust host (required for production)
330
307
  trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
331
308
 
332
- // Social providers
333
309
  socialProviders: Object.entries(authConfig.socialProviders)
334
310
  .filter(([_, config]) => config?.enabled !== false)
335
311
  .reduce(
@@ -345,10 +321,10 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
345
321
  {} as Record<string, { clientId: string; clientSecret: string }>,
346
322
  ),
347
323
 
348
- // Rate limiting configuration. `modelName`/`fields` are only forwarded
349
- // when `models.rateLimit` was derived (rateLimit.storage === 'database')
350
- // — mirroring the running instance's model options back to the table the
351
- // `RateLimit` Auth list was derived from.
324
+ // `modelName`/`fields` are only forwarded when `models.rateLimit` was
325
+ // derived (rateLimit.storage === 'database') — mirroring the running
326
+ // instance's model options back to the table the `RateLimit` Auth list
327
+ // was derived from.
352
328
  rateLimit: authConfig.rateLimit
353
329
  ? {
354
330
  enabled: authConfig.rateLimit.enabled,
@@ -361,12 +337,10 @@ export async function buildBetterAuthOptions<const TPlugins extends readonly Bet
361
337
  }
362
338
  : undefined,
363
339
 
364
- // Pass through any additional Better Auth plugins, then append
365
- // nextCookies LAST so it can write the Set-Cookie headers produced by
366
- // any auth.api.* call made inside a Next.js server action into Next's
367
- // cookie store. This is what makes the server-action auth forms (which
368
- // call auth.api.signInEmail/signUpEmail/etc. server-side) actually
369
- // persist a session. It must be the final plugin in the array.
340
+ // nextCookies must be LAST so it can write the Set-Cookie headers
341
+ // produced by any auth.api.* call inside a Next.js server action into
342
+ // Next's cookie store this is what makes the server-action auth forms
343
+ // actually persist a session.
370
344
  plugins: [...resolvedPlugins, nextCookies()],
371
345
  }
372
346
 
@@ -425,11 +399,9 @@ export function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(
425
399
  context: AccessContext | Promise<AccessContext>,
426
400
  plugins?: TPlugins,
427
401
  ): Auth<BetterAuthOptions> | Auth<ResolvedBetterAuthOptions<TPlugins>> {
428
- // Resolve config and context asynchronously
429
402
  const configPromise = Promise.resolve(opensaasConfig)
430
403
  const contextPromise = Promise.resolve(context)
431
404
 
432
- // Create auth instance lazily when needed
433
405
  type AuthInstance = Auth<BetterAuthOptions> | Auth<ResolvedBetterAuthOptions<TPlugins>>
434
406
  let authInstance: AuthInstance | null = null
435
407
  let authPromise: Promise<AuthInstance> | null = null
@@ -450,7 +422,6 @@ export function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(
450
422
  return authPromise
451
423
  }
452
424
 
453
- // Return a proxy that lazily initializes the auth instance
454
425
  return new Proxy({} as AuthInstance, {
455
426
  get(_, prop) {
456
427
  if (prop === 'then') {
@@ -458,7 +429,6 @@ export function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(
458
429
  return undefined
459
430
  }
460
431
 
461
- // Create a lazy wrapper function
462
432
  const lazyWrapper = async (...args: unknown[]) => {
463
433
  const instance = await getAuthInstance()
464
434
  const value = instance[prop as keyof typeof instance]
@@ -468,14 +438,12 @@ export function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(
468
438
  return value
469
439
  }
470
440
 
471
- // Return a proxy that supports both direct calls and nested property access
472
441
  return new Proxy(lazyWrapper, {
473
442
  get(target, subProp) {
474
443
  if (subProp === 'then') {
475
444
  // Support await on nested properties
476
445
  return undefined
477
446
  }
478
- // Handle nested property access (e.g., auth.api.getSession)
479
447
  return async (...args: unknown[]) => {
480
448
  const instance = await getAuthInstance()
481
449
  const parentValue = instance[prop as keyof typeof instance]
@@ -502,13 +470,6 @@ export function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(
502
470
  */
503
471
  const unresolvedSessionFieldWarnings = new Set<string>()
504
472
 
505
- /**
506
- * Warn (once per field, per process) that a `sessionFields` entry could not
507
- * be resolved from the session shape `auth.api.getSession()` actually
508
- * returned — naming the field and what keys were available to check, so the
509
- * gap is visible here instead of surfacing later as an access-control
510
- * function silently reading `undefined`.
511
- */
512
473
  function warnUnresolvedSessionField(field: string, resolvedSession: Record<string, unknown>): void {
513
474
  if (unresolvedSessionFieldWarnings.has(field)) return
514
475
  unresolvedSessionFieldWarnings.add(field)
@@ -527,17 +488,6 @@ function warnUnresolvedSessionField(field: string, resolvedSession: Record<strin
527
488
  )
528
489
  }
529
490
 
530
- /**
531
- * Resolve a single `sessionFields` entry off the resolved better-auth
532
- * session (whatever `auth.api.getSession()` returned — the default `{
533
- * session, user }` shape, or a `customSession` plugin's replaced shape).
534
- *
535
- * `userId` is special-cased to the authenticated user's `id` — the
536
- * documented default apps depend on. Every other name resolves against a
537
- * fixed precedence so a collision between sources is predictable rather
538
- * than incidental: a top-level key on the resolved session object, then the
539
- * `user` object, then the `session` sub-object.
540
- */
541
491
  function resolveSessionField(
542
492
  field: string,
543
493
  resolvedSession: Record<string, unknown>,
@@ -582,11 +532,8 @@ function resolveSessionField(
582
532
  * outage) into "anonymous" is indistinguishable from a mass sign-out under
583
533
  * fail-closed access control, so the caller must see it.
584
534
  *
585
- * Not called by any generated code before this helper existed — apps used to
586
- * hand-roll this same transform against `auth.api.getSession({ headers:
587
- * await headers() })`. Exported as the single reusable implementation; pass
588
- * the caller's request headers (e.g. Next.js `await headers()` in a Server
589
- * Component/action) so a session cookie can actually be resolved.
535
+ * Pass the caller's request headers (e.g. Next.js `await headers()` in a
536
+ * Server Component/action) so a session cookie can actually be resolved.
590
537
  *
591
538
  * `auth` is typed structurally over just the one member this function reads
592
539
  * — `api.getSession` — rather than a single concrete `Auth<Options>`
@@ -25,9 +25,6 @@ export type ForgotPasswordFormProps = {
25
25
  }
26
26
 
27
27
  /**
28
- * Forgot password form component
29
- * Allows users to request a password reset email
30
- *
31
28
  * Submits through an app-owned server action rather than calling the auth API
32
29
  * from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
33
30
  *
@@ -42,9 +42,6 @@ export type ResetPasswordFormProps = {
42
42
  }
43
43
 
44
44
  /**
45
- * Reset password form component
46
- * Completes a password reset using the token from the reset email.
47
- *
48
45
  * Submits through an app-owned server action rather than calling the auth API
49
46
  * from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
50
47
  *
@@ -78,8 +75,6 @@ export function ResetPasswordForm({
78
75
  const [error, setError] = useState('')
79
76
  const [loading, setLoading] = useState(false)
80
77
 
81
- // Guard: no token means the user hit this page directly or followed a
82
- // malformed/expired link. Don't render a form that can't succeed.
83
78
  if (!token) {
84
79
  return (
85
80
  <div className={`w-full max-w-md mx-auto p-6 ${className}`}>
@@ -46,9 +46,6 @@ export type SignInFormProps = {
46
46
  }
47
47
 
48
48
  /**
49
- * Sign in form component
50
- * Provides email/password sign in and OAuth provider buttons
51
- *
52
49
  * Submits through app-owned server actions rather than calling the auth API
53
50
  * from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
54
51
  *
@@ -96,7 +93,6 @@ export function SignInForm({
96
93
  throw new Error(cleanAuthErrorMessage(result.error, 'Sign in failed'))
97
94
  }
98
95
 
99
- // If onSuccess is provided, call it. Otherwise, automatically redirect
100
96
  if (onSuccess) {
101
97
  onSuccess()
102
98
  } else {
@@ -51,9 +51,6 @@ export type SignUpFormProps = {
51
51
  }
52
52
 
53
53
  /**
54
- * Sign up form component
55
- * Provides email/password registration and OAuth provider buttons
56
- *
57
54
  * Submits through app-owned server actions rather than calling the auth API
58
55
  * from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
59
56
  *
@@ -90,7 +87,6 @@ export function SignUpForm({
90
87
  e.preventDefault()
91
88
  setError('')
92
89
 
93
- // Validate password confirmation
94
90
  if (requirePasswordConfirmation && password !== confirmPassword) {
95
91
  setError('Passwords do not match')
96
92
  return
@@ -105,7 +101,6 @@ export function SignUpForm({
105
101
  throw new Error(cleanAuthErrorMessage(result.error, 'Sign up failed'))
106
102
  }
107
103
 
108
- // If onSuccess is provided, call it. Otherwise, automatically redirect
109
104
  if (onSuccess) {
110
105
  onSuccess()
111
106
  } else {
@@ -1,10 +1,7 @@
1
1
  /**
2
- * Strip better-call's `[body.field]` validation prefixes from an auth error
3
- * message so forms can display something user-friendly.
4
- *
5
- * Better-auth surfaces validation errors like `[body.password] Password is too
6
- * short`. This removes those bracketed prefixes and normalises whitespace,
7
- * falling back to a default when the message is empty or missing.
2
+ * Strip better-call's `[body.field]` validation prefixes (e.g. `[body.password]
3
+ * Password is too short`) from an auth error message so forms can display
4
+ * something user-friendly.
8
5
  *
9
6
  * Internal to the auth forms — not part of the package's public contract.
10
7
  */
package/src/ui/types.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  /**
2
2
  * Contract types shared between the pre-built auth forms and the app-owned
3
- * server actions they invoke.
4
- *
5
- * The forms live in this package but never own an `auth` instance. Instead each
6
- * form receives **Auth actions** as props — `'use server'` functions the app
7
- * defines against its own better-auth instance. These types are the agreement
8
- * between the two: the app implements actions matching them, the forms call
9
- * actions matching them.
3
+ * `'use server'` actions they invoke instead of owning an `authClient`. See
4
+ * ADR-0020.
10
5
  */
11
6
 
12
7
  /**
@@ -15,30 +10,22 @@
15
10
  */
16
11
  export type AuthActionResult = { success: true } | { success: false; error: string }
17
12
 
18
- /** Input to the sign-in action. */
19
13
  export type SignInInput = { email: string; password: string }
20
14
 
21
- /** Input to the sign-up action. */
22
15
  export type SignUpInput = { name: string; email: string; password: string }
23
16
 
24
- /** Input to the request-password-reset action. */
25
17
  export type RequestPasswordResetInput = { email: string }
26
18
 
27
- /** Input to the reset-password action. */
28
19
  export type ResetPasswordInput = { token: string; password: string }
29
20
 
30
- /** Signs a user in with email + password. */
31
21
  export type SignInAction = (input: SignInInput) => Promise<AuthActionResult>
32
22
 
33
- /** Creates an account with email + password. */
34
23
  export type SignUpAction = (input: SignUpInput) => Promise<AuthActionResult>
35
24
 
36
- /** Requests a password-reset email. */
37
25
  export type RequestPasswordResetAction = (
38
26
  input: RequestPasswordResetInput,
39
27
  ) => Promise<AuthActionResult>
40
28
 
41
- /** Completes a password reset using a token from the reset email. */
42
29
  export type ResetPasswordAction = (input: ResetPasswordInput) => Promise<AuthActionResult>
43
30
 
44
31
  /**