@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +151 -0
- package/CLAUDE.md +140 -22
- package/dist/client/index.d.ts +0 -16
- package/dist/client/index.d.ts.map +1 -1
- package/dist/config/adopt-better-auth-tables.d.ts +4 -49
- package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
- package/dist/config/adopt-better-auth-tables.js +4 -51
- package/dist/config/adopt-better-auth-tables.js.map +1 -1
- package/dist/config/derive-auth-lists.d.ts +42 -37
- package/dist/config/derive-auth-lists.d.ts.map +1 -1
- package/dist/config/derive-auth-lists.js +579 -223
- package/dist/config/derive-auth-lists.js.map +1 -1
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +4 -34
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin.d.ts.map +1 -1
- package/dist/config/plugin.js +20 -90
- package/dist/config/plugin.js.map +1 -1
- package/dist/config/types.d.ts +89 -67
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -13
- package/dist/index.js.map +1 -1
- package/dist/lists/index.d.ts +9 -5
- package/dist/lists/index.d.ts.map +1 -1
- package/dist/lists/index.js +9 -6
- package/dist/lists/index.js.map +1 -1
- package/dist/mcp/better-auth.d.ts +6 -19
- package/dist/mcp/better-auth.d.ts.map +1 -1
- package/dist/mcp/better-auth.js +6 -20
- package/dist/mcp/better-auth.js.map +1 -1
- package/dist/plugins/index.d.ts +4 -4
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +4 -4
- package/dist/plugins/index.js.map +1 -1
- package/dist/runtime/types.d.ts +2 -9
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js +0 -4
- package/dist/runtime/types.js.map +1 -1
- package/dist/server/index.d.ts +5 -12
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +14 -62
- package/dist/server/index.js.map +1 -1
- package/dist/ui/components/ForgotPasswordForm.d.ts +0 -3
- package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -1
- package/dist/ui/components/ForgotPasswordForm.js +0 -3
- package/dist/ui/components/ForgotPasswordForm.js.map +1 -1
- package/dist/ui/components/ResetPasswordForm.d.ts +0 -3
- package/dist/ui/components/ResetPasswordForm.d.ts.map +1 -1
- package/dist/ui/components/ResetPasswordForm.js +0 -5
- package/dist/ui/components/ResetPasswordForm.js.map +1 -1
- package/dist/ui/components/SignInForm.d.ts +0 -3
- package/dist/ui/components/SignInForm.d.ts.map +1 -1
- package/dist/ui/components/SignInForm.js +0 -4
- package/dist/ui/components/SignInForm.js.map +1 -1
- package/dist/ui/components/SignUpForm.d.ts +0 -3
- package/dist/ui/components/SignUpForm.d.ts.map +1 -1
- package/dist/ui/components/SignUpForm.js +0 -5
- package/dist/ui/components/SignUpForm.js.map +1 -1
- package/dist/ui/lib/clean-error-message.d.ts +3 -6
- package/dist/ui/lib/clean-error-message.d.ts.map +1 -1
- package/dist/ui/lib/clean-error-message.js +3 -6
- package/dist/ui/lib/clean-error-message.js.map +1 -1
- package/dist/ui/types.d.ts +2 -15
- package/dist/ui/types.d.ts.map +1 -1
- package/dist/ui/types.js +2 -7
- package/dist/ui/types.js.map +1 -1
- package/package.json +12 -5
- package/src/client/index.ts +0 -17
- package/src/config/adopt-better-auth-tables.ts +4 -51
- package/src/config/derive-auth-lists.ts +685 -252
- package/src/config/index.ts +4 -34
- package/src/config/plugin.ts +21 -91
- package/src/config/types.ts +90 -67
- package/src/index.ts +3 -14
- package/src/lists/index.ts +17 -5
- package/src/mcp/better-auth.ts +6 -25
- package/src/plugins/index.ts +4 -5
- package/src/runtime/types.ts +2 -10
- package/src/server/index.ts +17 -70
- package/src/ui/components/ForgotPasswordForm.tsx +0 -3
- package/src/ui/components/ResetPasswordForm.tsx +0 -5
- package/src/ui/components/SignInForm.tsx +0 -4
- package/src/ui/components/SignUpForm.tsx +0 -5
- package/src/ui/lib/clean-error-message.ts +3 -6
- package/src/ui/types.ts +2 -15
- package/tests/auth-lists-drift.test.ts +480 -0
- package/tests/config.test.ts +3 -0
- package/tests/credential-field-read-deny-e2e.test.ts +295 -0
- package/tests/derive-auth-lists.test.ts +263 -6
- package/tests/generated-fk-shape.test.ts +432 -9
- package/tests/mcp-oauth-cascade-e2e.test.ts +267 -0
- package/tests/plugin-derived-keys.test.ts +39 -0
- package/tests/plugin-table-derivation.test.ts +270 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/server/schema-converter.d.ts +0 -81
- package/dist/server/schema-converter.d.ts.map +0 -1
- package/dist/server/schema-converter.js +0 -157
- package/dist/server/schema-converter.js.map +0 -1
- package/src/server/schema-converter.ts +0 -243
- package/tests/schema-converter.test.ts +0 -399
package/src/mcp/better-auth.ts
CHANGED
|
@@ -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
|
-
*
|
|
131
|
-
* Exposes OAuth authorization server metadata for MCP clients
|
|
115
|
+
* Exposes OAuth authorization server metadata for MCP clients.
|
|
132
116
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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
|
-
*
|
|
151
|
-
* Exposes OAuth protected resource metadata for MCP clients
|
|
133
|
+
* Exposes OAuth protected resource metadata for MCP clients.
|
|
152
134
|
*
|
|
153
|
-
*
|
|
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
|
|
package/src/plugins/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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'
|
package/src/runtime/types.ts
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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
|
*
|
package/src/server/index.ts
CHANGED
|
@@ -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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
70
|
-
*
|
|
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
|
-
//
|
|
320
|
-
//
|
|
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
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
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
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
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
|
-
*
|
|
586
|
-
*
|
|
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
|
|
3
|
-
* message so forms can display
|
|
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
|
/**
|