@opensaas/stack-auth 0.31.0 → 0.32.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 +54 -0
- package/CLAUDE.md +42 -7
- package/README.md +64 -18
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -2
- package/dist/server/index.js.map +1 -1
- package/dist/ui/components/ForgotPasswordForm.d.ts +10 -7
- package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -1
- package/dist/ui/components/ForgotPasswordForm.js +11 -10
- package/dist/ui/components/ForgotPasswordForm.js.map +1 -1
- package/dist/ui/components/ResetPasswordForm.d.ts +60 -0
- package/dist/ui/components/ResetPasswordForm.d.ts.map +1 -0
- package/dist/ui/components/ResetPasswordForm.js +70 -0
- package/dist/ui/components/ResetPasswordForm.js.map +1 -0
- package/dist/ui/components/SignInForm.d.ts +22 -9
- package/dist/ui/components/SignInForm.d.ts.map +1 -1
- package/dist/ui/components/SignInForm.js +25 -19
- package/dist/ui/components/SignInForm.js.map +1 -1
- package/dist/ui/components/SignUpForm.d.ts +16 -8
- package/dist/ui/components/SignUpForm.d.ts.map +1 -1
- package/dist/ui/components/SignUpForm.js +19 -23
- package/dist/ui/components/SignUpForm.js.map +1 -1
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/lib/clean-error-message.d.ts +12 -0
- package/dist/ui/lib/clean-error-message.d.ts.map +1 -0
- package/dist/ui/lib/clean-error-message.js +20 -0
- package/dist/ui/lib/clean-error-message.js.map +1 -0
- package/dist/ui/types.d.ts +55 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +12 -0
- package/dist/ui/types.js.map +1 -0
- package/package.json +3 -3
- package/src/server/index.ts +8 -2
- package/src/ui/components/ForgotPasswordForm.tsx +18 -14
- package/src/ui/components/ResetPasswordForm.tsx +182 -0
- package/src/ui/components/SignInForm.tsx +43 -26
- package/src/ui/components/SignUpForm.tsx +37 -29
- package/src/ui/index.ts +17 -0
- package/src/ui/lib/clean-error-message.ts +21 -0
- package/src/ui/types.ts +49 -0
- package/tests/clean-error-message.test.ts +29 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @opensaas/stack-auth
|
|
2
2
|
|
|
3
|
+
## 0.32.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#813](https://github.com/OpenSaasAU/stack/pull/813) [`5a6198c`](https://github.com/OpenSaasAU/stack/commit/5a6198c9489641e4b1ad542a3181c15e750f7d85) Thanks [@borisno2](https://github.com/borisno2)! - Auth forms now submit through app-owned server actions instead of the browser `authClient`
|
|
8
|
+
|
|
9
|
+
The pre-built auth forms (`SignInForm`, `SignUpForm`, `ForgotPasswordForm`, and the new
|
|
10
|
+
`ResetPasswordForm`) no longer take an `authClient` prop that calls `/api/auth/*` from the
|
|
11
|
+
browser. Instead each form takes **server action** props — `'use server'` functions the app
|
|
12
|
+
defines against its own `auth` instance. This keeps the auth network surface server-side and
|
|
13
|
+
matches the app's existing `lib/actions/*` convention. `createAuth` now auto-adds
|
|
14
|
+
better-auth's `nextCookies` plugin, so the session cookie set inside a server action persists.
|
|
15
|
+
See ADR-0020.
|
|
16
|
+
|
|
17
|
+
The package exports the action contract types (`AuthActionResult`, `SignInInput`,
|
|
18
|
+
`SignUpInput`, `RequestPasswordResetInput`, `ResetPasswordInput`, and the action aliases).
|
|
19
|
+
`createClient` is unchanged for client-side session reading (`useSession`).
|
|
20
|
+
|
|
21
|
+
Migration — define the actions in your app and pass them to the forms:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// lib/actions/auth.ts
|
|
25
|
+
'use server'
|
|
26
|
+
import { headers } from 'next/headers'
|
|
27
|
+
import { auth } from '@/lib/auth'
|
|
28
|
+
import type { AuthActionResult, SignInInput } from '@opensaas/stack-auth/ui'
|
|
29
|
+
|
|
30
|
+
export async function signInAction(input: SignInInput): Promise<AuthActionResult> {
|
|
31
|
+
try {
|
|
32
|
+
await auth.api.signInEmail({
|
|
33
|
+
body: { email: input.email, password: input.password },
|
|
34
|
+
headers: await headers(),
|
|
35
|
+
})
|
|
36
|
+
return { success: true }
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return { success: false, error: err instanceof Error ? err.message : 'Sign in failed' }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Before
|
|
45
|
+
<SignInForm authClient={authClient} redirectTo="/admin" />
|
|
46
|
+
|
|
47
|
+
// After
|
|
48
|
+
<SignInForm signInAction={signInAction} redirectTo="/admin" />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Social sign-in becomes a redirecting server action passed as `signInSocialAction`. The CLI
|
|
52
|
+
feature-generator now scaffolds `lib/actions/auth.ts` and a `reset-password` page, and no
|
|
53
|
+
longer emits `lib/auth-client.ts`.
|
|
54
|
+
|
|
55
|
+
## 0.31.1
|
|
56
|
+
|
|
3
57
|
## 0.31.0
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/CLAUDE.md
CHANGED
|
@@ -38,11 +38,13 @@ Auto-generated lists:
|
|
|
38
38
|
|
|
39
39
|
### UI (`src/ui/index.ts`)
|
|
40
40
|
|
|
41
|
-
Pre-built forms (client components)
|
|
41
|
+
Pre-built forms (client components). Each takes **server action** props (not an
|
|
42
|
+
`authClient`) — see "Auth forms submit through server actions" below and ADR-0020:
|
|
42
43
|
|
|
43
44
|
- `SignInForm` - Email/password + OAuth sign in
|
|
44
45
|
- `SignUpForm` - Create account with password confirmation
|
|
45
46
|
- `ForgotPasswordForm` - Request password reset email
|
|
47
|
+
- `ResetPasswordForm` - Set a new password from a reset-email token
|
|
46
48
|
|
|
47
49
|
### Plugins (`src/plugins/index.ts`)
|
|
48
50
|
|
|
@@ -334,18 +336,47 @@ export default config({
|
|
|
334
336
|
})
|
|
335
337
|
|
|
336
338
|
// 2. Server (lib/auth.ts)
|
|
337
|
-
export const auth = createAuth(config)
|
|
339
|
+
export const auth = createAuth(config, rawOpensaasContext)
|
|
338
340
|
|
|
339
341
|
// 3. Route (app/api/auth/[...all]/route.ts)
|
|
340
342
|
export { GET, POST } from '@/lib/auth'
|
|
341
343
|
|
|
342
|
-
// 4.
|
|
343
|
-
|
|
344
|
+
// 4. Server actions (lib/actions/auth.ts) — the forms submit through these
|
|
345
|
+
'use server'
|
|
346
|
+
import { auth } from '@/lib/auth'
|
|
347
|
+
import type { AuthActionResult, SignInInput } from '@opensaas/stack-auth/ui'
|
|
348
|
+
export async function signInAction(input: SignInInput): Promise<AuthActionResult> {
|
|
349
|
+
try {
|
|
350
|
+
await auth.api.signInEmail({ body: input, headers: await headers() })
|
|
351
|
+
return { success: true }
|
|
352
|
+
} catch (err) {
|
|
353
|
+
return { success: false, error: err instanceof Error ? err.message : 'Sign in failed' }
|
|
354
|
+
}
|
|
355
|
+
}
|
|
344
356
|
|
|
345
357
|
// 5. UI (app/sign-in/page.tsx)
|
|
346
|
-
<SignInForm
|
|
358
|
+
<SignInForm signInAction={signInAction} redirectTo="/admin" />
|
|
347
359
|
```
|
|
348
360
|
|
|
361
|
+
### Auth forms submit through server actions
|
|
362
|
+
|
|
363
|
+
The pre-built forms take app-owned `'use server'` action props instead of a browser
|
|
364
|
+
`authClient`, so the auth network surface stays server-side (no `/api/auth/*` call from
|
|
365
|
+
the browser). `createAuth` auto-adds better-auth's `nextCookies` plugin (as the last
|
|
366
|
+
plugin) so a session cookie set inside a server action persists. The action props are:
|
|
367
|
+
|
|
368
|
+
- `SignInForm`: `signInAction`, optional `signInSocialAction`
|
|
369
|
+
- `SignUpForm`: `signUpAction`, optional `signInSocialAction`
|
|
370
|
+
- `ForgotPasswordForm`: `requestPasswordResetAction`
|
|
371
|
+
- `ResetPasswordForm`: `resetPasswordAction` + a `token` prop (the page reads it from
|
|
372
|
+
`searchParams.token`)
|
|
373
|
+
|
|
374
|
+
Email actions return `AuthActionResult` and the form redirects client-side; social
|
|
375
|
+
sign-in redirects server-side to the provider. The package exports the contract types
|
|
376
|
+
(`AuthActionResult`, `SignInInput`, `SignUpInput`, `RequestPasswordResetInput`,
|
|
377
|
+
`ResetPasswordInput`, and the action aliases). `createClient` is unchanged for
|
|
378
|
+
client-side session reading (`useSession`). See ADR-0020 and `examples/starter-auth`.
|
|
379
|
+
|
|
349
380
|
### Access Control with Session
|
|
350
381
|
|
|
351
382
|
```typescript
|
|
@@ -376,8 +407,12 @@ authPlugin({
|
|
|
376
407
|
}
|
|
377
408
|
})
|
|
378
409
|
|
|
379
|
-
// UI
|
|
380
|
-
<SignInForm
|
|
410
|
+
// UI — pass the redirecting social action to enable the provider buttons
|
|
411
|
+
<SignInForm
|
|
412
|
+
signInAction={signInAction}
|
|
413
|
+
signInSocialAction={signInSocialAction}
|
|
414
|
+
socialProviders={['github', 'google']}
|
|
415
|
+
/>
|
|
381
416
|
```
|
|
382
417
|
|
|
383
418
|
## Type Safety
|
package/README.md
CHANGED
|
@@ -77,17 +77,30 @@ export const POST = auth.handler
|
|
|
77
77
|
export { GET, POST } from '@/lib/auth'
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
### 5. Create Auth
|
|
80
|
+
### 5. Create Auth Server Actions
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
import { createClient } from '@opensaas/stack-auth/client'
|
|
82
|
+
The pre-built forms submit through app-owned server actions (calling better-auth's
|
|
83
|
+
server API directly) instead of a browser client. `createAuth` auto-adds better-auth's
|
|
84
|
+
`nextCookies` plugin, so the session cookie set inside these actions persists.
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
```typescript
|
|
87
|
+
// lib/actions/auth.ts
|
|
88
|
+
'use server'
|
|
89
|
+
|
|
90
|
+
import { headers } from 'next/headers'
|
|
91
|
+
import { auth } from '@/lib/auth'
|
|
92
|
+
import type { AuthActionResult, SignInInput } from '@opensaas/stack-auth/ui'
|
|
93
|
+
|
|
94
|
+
export async function signInAction(input: SignInInput): Promise<AuthActionResult> {
|
|
95
|
+
try {
|
|
96
|
+
await auth.api.signInEmail({ body: input, headers: await headers() })
|
|
97
|
+
return { success: true }
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { success: false, error: err instanceof Error ? err.message : 'Sign in failed' }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// ...and signUpAction / requestPasswordResetAction / resetPasswordAction — see
|
|
103
|
+
// examples/starter-auth/lib/actions/auth.ts for the full set.
|
|
91
104
|
```
|
|
92
105
|
|
|
93
106
|
### 6. Add Sign In Page
|
|
@@ -95,12 +108,12 @@ export const authClient = createClient({
|
|
|
95
108
|
```typescript
|
|
96
109
|
// app/sign-in/page.tsx
|
|
97
110
|
import { SignInForm } from '@opensaas/stack-auth/ui'
|
|
98
|
-
import {
|
|
111
|
+
import { signInAction } from '@/lib/actions/auth'
|
|
99
112
|
|
|
100
113
|
export default function SignInPage() {
|
|
101
114
|
return (
|
|
102
115
|
<div className="min-h-screen flex items-center justify-center">
|
|
103
|
-
<SignInForm
|
|
116
|
+
<SignInForm signInAction={signInAction} redirectTo="/admin" />
|
|
104
117
|
</div>
|
|
105
118
|
)
|
|
106
119
|
}
|
|
@@ -238,14 +251,18 @@ the full migrator walkthrough and a Schema-parity (clean-diff) check.
|
|
|
238
251
|
|
|
239
252
|
## UI Components
|
|
240
253
|
|
|
254
|
+
Each form takes app-owned server action props (defined in `lib/actions/auth.ts`),
|
|
255
|
+
not an `authClient`. See ADR-0020 and `examples/starter-auth`.
|
|
256
|
+
|
|
241
257
|
### SignInForm
|
|
242
258
|
|
|
243
259
|
```typescript
|
|
244
260
|
import { SignInForm } from '@opensaas/stack-auth/ui'
|
|
245
|
-
import {
|
|
261
|
+
import { signInAction, signInSocialAction } from '@/lib/actions/auth'
|
|
246
262
|
|
|
247
263
|
<SignInForm
|
|
248
|
-
|
|
264
|
+
signInAction={signInAction}
|
|
265
|
+
signInSocialAction={signInSocialAction}
|
|
249
266
|
redirectTo="/dashboard"
|
|
250
267
|
showSocialProviders={true}
|
|
251
268
|
socialProviders={['github', 'google']}
|
|
@@ -258,10 +275,10 @@ import { authClient } from '@/lib/auth-client'
|
|
|
258
275
|
|
|
259
276
|
```typescript
|
|
260
277
|
import { SignUpForm } from '@opensaas/stack-auth/ui'
|
|
261
|
-
import {
|
|
278
|
+
import { signUpAction } from '@/lib/actions/auth'
|
|
262
279
|
|
|
263
280
|
<SignUpForm
|
|
264
|
-
|
|
281
|
+
signUpAction={signUpAction}
|
|
265
282
|
redirectTo="/dashboard"
|
|
266
283
|
requirePasswordConfirmation={true}
|
|
267
284
|
onSuccess={() => console.log('Account created!')}
|
|
@@ -272,14 +289,31 @@ import { authClient } from '@/lib/auth-client'
|
|
|
272
289
|
|
|
273
290
|
```typescript
|
|
274
291
|
import { ForgotPasswordForm } from '@opensaas/stack-auth/ui'
|
|
275
|
-
import {
|
|
292
|
+
import { requestPasswordResetAction } from '@/lib/actions/auth'
|
|
276
293
|
|
|
277
294
|
<ForgotPasswordForm
|
|
278
|
-
|
|
295
|
+
requestPasswordResetAction={requestPasswordResetAction}
|
|
279
296
|
onSuccess={() => console.log('Reset email sent!')}
|
|
280
297
|
/>
|
|
281
298
|
```
|
|
282
299
|
|
|
300
|
+
### ResetPasswordForm
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// app/reset-password/page.tsx
|
|
304
|
+
import { ResetPasswordForm } from '@opensaas/stack-auth/ui'
|
|
305
|
+
import { resetPasswordAction } from '@/lib/actions/auth'
|
|
306
|
+
|
|
307
|
+
export default async function ResetPasswordPage({
|
|
308
|
+
searchParams,
|
|
309
|
+
}: {
|
|
310
|
+
searchParams: Promise<{ token?: string }>
|
|
311
|
+
}) {
|
|
312
|
+
const { token } = await searchParams
|
|
313
|
+
return <ResetPasswordForm resetPasswordAction={resetPasswordAction} token={token ?? ''} />
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
283
317
|
## Auto-Generated Lists
|
|
284
318
|
|
|
285
319
|
The following lists are automatically created when you use `authPlugin()`:
|
|
@@ -404,7 +438,19 @@ access: {
|
|
|
404
438
|
|
|
405
439
|
## Client-Side Hooks
|
|
406
440
|
|
|
407
|
-
|
|
441
|
+
The auth forms no longer need a browser client (they use server actions). To read the
|
|
442
|
+
session on the client, create a client yourself with `createClient` and use its hooks:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// lib/auth-client.ts
|
|
446
|
+
'use client'
|
|
447
|
+
|
|
448
|
+
import { createClient } from '@opensaas/stack-auth/client'
|
|
449
|
+
|
|
450
|
+
export const authClient = createClient({
|
|
451
|
+
baseURL: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
452
|
+
})
|
|
453
|
+
```
|
|
408
454
|
|
|
409
455
|
```typescript
|
|
410
456
|
'use client'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAgCzE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,iDA8IhD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EACnC,aAAa,EAAE,MAAM,EAAE,2CA0BxB;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { betterAuth } from 'better-auth';
|
|
2
2
|
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
|
3
|
+
import { nextCookies } from 'better-auth/next-js';
|
|
3
4
|
/**
|
|
4
5
|
* Get better-auth database configuration from OpenSaas config
|
|
5
6
|
*/
|
|
@@ -101,8 +102,13 @@ export function createAuth(opensaasConfig, context) {
|
|
|
101
102
|
max: authConfig.rateLimit.max,
|
|
102
103
|
}
|
|
103
104
|
: undefined,
|
|
104
|
-
// Pass through any additional Better Auth plugins
|
|
105
|
-
|
|
105
|
+
// Pass through any additional Better Auth plugins, then append
|
|
106
|
+
// nextCookies LAST so it can write the Set-Cookie headers produced by
|
|
107
|
+
// any auth.api.* call made inside a Next.js server action into Next's
|
|
108
|
+
// cookie store. This is what makes the server-action auth forms (which
|
|
109
|
+
// call auth.api.signInEmail/signUpEmail/etc. server-side) actually
|
|
110
|
+
// persist a session. It must be the final plugin in the array.
|
|
111
|
+
plugins: [...(authConfig.betterAuthPlugins || []), nextCookies()],
|
|
106
112
|
};
|
|
107
113
|
authInstance = betterAuth(betterAuthConfig);
|
|
108
114
|
return authInstance;
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RequestPasswordResetAction } from '../types.js';
|
|
2
2
|
export type ForgotPasswordFormProps = {
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Server action that requests a password-reset email.
|
|
5
|
+
* Define it in your app (`'use server'`) against your own auth instance.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
requestPasswordResetAction: RequestPasswordResetAction;
|
|
8
8
|
/**
|
|
9
9
|
* Custom CSS class for the form container
|
|
10
10
|
*/
|
|
@@ -22,15 +22,18 @@ export type ForgotPasswordFormProps = {
|
|
|
22
22
|
* Forgot password form component
|
|
23
23
|
* Allows users to request a password reset email
|
|
24
24
|
*
|
|
25
|
+
* Submits through an app-owned server action rather than calling the auth API
|
|
26
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
27
|
+
*
|
|
25
28
|
* @example
|
|
26
29
|
* ```typescript
|
|
27
30
|
* import { ForgotPasswordForm } from '@opensaas/stack-auth/ui'
|
|
28
|
-
* import {
|
|
31
|
+
* import { requestPasswordResetAction } from '@/lib/actions/auth'
|
|
29
32
|
*
|
|
30
33
|
* export default function ForgotPasswordPage() {
|
|
31
|
-
* return <ForgotPasswordForm
|
|
34
|
+
* return <ForgotPasswordForm requestPasswordResetAction={requestPasswordResetAction} />
|
|
32
35
|
* }
|
|
33
36
|
* ```
|
|
34
37
|
*/
|
|
35
|
-
export declare function ForgotPasswordForm({
|
|
38
|
+
export declare function ForgotPasswordForm({ requestPasswordResetAction, className, onSuccess, onError, }: ForgotPasswordFormProps): import("react/jsx-runtime").JSX.Element;
|
|
36
39
|
//# sourceMappingURL=ForgotPasswordForm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ForgotPasswordForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/ForgotPasswordForm.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ForgotPasswordForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/ForgotPasswordForm.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAE7D,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;OAGG;IACH,0BAA0B,EAAE,0BAA0B,CAAA;IACtD;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,0BAA0B,EAC1B,SAAc,EACd,SAAS,EACT,OAAO,GACR,EAAE,uBAAuB,2CA2EzB"}
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
+
import { cleanAuthErrorMessage } from '../lib/clean-error-message.js';
|
|
4
5
|
/**
|
|
5
6
|
* Forgot password form component
|
|
6
7
|
* Allows users to request a password reset email
|
|
7
8
|
*
|
|
9
|
+
* Submits through an app-owned server action rather than calling the auth API
|
|
10
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
11
|
+
*
|
|
8
12
|
* @example
|
|
9
13
|
* ```typescript
|
|
10
14
|
* import { ForgotPasswordForm } from '@opensaas/stack-auth/ui'
|
|
11
|
-
* import {
|
|
15
|
+
* import { requestPasswordResetAction } from '@/lib/actions/auth'
|
|
12
16
|
*
|
|
13
17
|
* export default function ForgotPasswordPage() {
|
|
14
|
-
* return <ForgotPasswordForm
|
|
18
|
+
* return <ForgotPasswordForm requestPasswordResetAction={requestPasswordResetAction} />
|
|
15
19
|
* }
|
|
16
20
|
* ```
|
|
17
21
|
*/
|
|
18
|
-
export function ForgotPasswordForm({
|
|
22
|
+
export function ForgotPasswordForm({ requestPasswordResetAction, className = '', onSuccess, onError, }) {
|
|
19
23
|
const [email, setEmail] = useState('');
|
|
20
24
|
const [error, setError] = useState('');
|
|
21
25
|
const [success, setSuccess] = useState(false);
|
|
@@ -26,18 +30,15 @@ export function ForgotPasswordForm({ authClient, className = '', onSuccess, onEr
|
|
|
26
30
|
setSuccess(false);
|
|
27
31
|
setLoading(true);
|
|
28
32
|
try {
|
|
29
|
-
const result = await
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
if (result.error) {
|
|
34
|
-
throw new Error(result.error.message);
|
|
33
|
+
const result = await requestPasswordResetAction({ email });
|
|
34
|
+
if (!result.success) {
|
|
35
|
+
throw new Error(cleanAuthErrorMessage(result.error, 'Failed to send reset email'));
|
|
35
36
|
}
|
|
36
37
|
setSuccess(true);
|
|
37
38
|
onSuccess?.();
|
|
38
39
|
}
|
|
39
40
|
catch (err) {
|
|
40
|
-
const message = err instanceof Error ? err.message : 'Failed to send reset email';
|
|
41
|
+
const message = cleanAuthErrorMessage(err instanceof Error ? err.message : undefined, 'Failed to send reset email');
|
|
41
42
|
setError(message);
|
|
42
43
|
onError?.(err instanceof Error ? err : new Error(message));
|
|
43
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ForgotPasswordForm.js","sourceRoot":"","sources":["../../../src/ui/components/ForgotPasswordForm.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ForgotPasswordForm.js","sourceRoot":"","sources":["../../../src/ui/components/ForgotPasswordForm.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ResetPasswordAction } from '../types.js';
|
|
2
|
+
export type ResetPasswordFormProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Server action that completes the password reset.
|
|
5
|
+
* Define it in your app (`'use server'`) against your own auth instance.
|
|
6
|
+
*/
|
|
7
|
+
resetPasswordAction: ResetPasswordAction;
|
|
8
|
+
/**
|
|
9
|
+
* The reset token from the email link. The page reads it from
|
|
10
|
+
* `searchParams.token` and passes it in. When empty, the form renders an
|
|
11
|
+
* "invalid or expired link" state instead of a password form.
|
|
12
|
+
*/
|
|
13
|
+
token: string;
|
|
14
|
+
/**
|
|
15
|
+
* URL to redirect to after a successful reset
|
|
16
|
+
* @default '/sign-in'
|
|
17
|
+
*/
|
|
18
|
+
redirectTo?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Require password confirmation
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
requirePasswordConfirmation?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Custom CSS class for the form container
|
|
26
|
+
*/
|
|
27
|
+
className?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Callback when the reset succeeds
|
|
30
|
+
*/
|
|
31
|
+
onSuccess?: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Callback when the reset fails
|
|
34
|
+
*/
|
|
35
|
+
onError?: (error: Error) => void;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Reset password form component
|
|
39
|
+
* Completes a password reset using the token from the reset email.
|
|
40
|
+
*
|
|
41
|
+
* Submits through an app-owned server action rather than calling the auth API
|
|
42
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* import { ResetPasswordForm } from '@opensaas/stack-auth/ui'
|
|
47
|
+
* import { resetPasswordAction } from '@/lib/actions/auth'
|
|
48
|
+
*
|
|
49
|
+
* export default async function ResetPasswordPage({
|
|
50
|
+
* searchParams,
|
|
51
|
+
* }: {
|
|
52
|
+
* searchParams: Promise<{ token?: string }>
|
|
53
|
+
* }) {
|
|
54
|
+
* const { token } = await searchParams
|
|
55
|
+
* return <ResetPasswordForm resetPasswordAction={resetPasswordAction} token={token ?? ''} />
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function ResetPasswordForm({ resetPasswordAction, token, redirectTo, requirePasswordConfirmation, className, onSuccess, onError, }: ResetPasswordFormProps): import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
//# sourceMappingURL=ResetPasswordForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResetPasswordForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/ResetPasswordForm.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;OAGG;IACH,mBAAmB,EAAE,mBAAmB,CAAA;IACxC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,mBAAmB,EACnB,KAAK,EACL,UAAuB,EACvB,2BAAkC,EAClC,SAAc,EACd,SAAS,EACT,OAAO,GACR,EAAE,sBAAsB,2CA4GxB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { cleanAuthErrorMessage } from '../lib/clean-error-message.js';
|
|
6
|
+
/**
|
|
7
|
+
* Reset password form component
|
|
8
|
+
* Completes a password reset using the token from the reset email.
|
|
9
|
+
*
|
|
10
|
+
* Submits through an app-owned server action rather than calling the auth API
|
|
11
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { ResetPasswordForm } from '@opensaas/stack-auth/ui'
|
|
16
|
+
* import { resetPasswordAction } from '@/lib/actions/auth'
|
|
17
|
+
*
|
|
18
|
+
* export default async function ResetPasswordPage({
|
|
19
|
+
* searchParams,
|
|
20
|
+
* }: {
|
|
21
|
+
* searchParams: Promise<{ token?: string }>
|
|
22
|
+
* }) {
|
|
23
|
+
* const { token } = await searchParams
|
|
24
|
+
* return <ResetPasswordForm resetPasswordAction={resetPasswordAction} token={token ?? ''} />
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function ResetPasswordForm({ resetPasswordAction, token, redirectTo = '/sign-in', requirePasswordConfirmation = true, className = '', onSuccess, onError, }) {
|
|
29
|
+
const router = useRouter();
|
|
30
|
+
const [password, setPassword] = useState('');
|
|
31
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
32
|
+
const [error, setError] = useState('');
|
|
33
|
+
const [loading, setLoading] = useState(false);
|
|
34
|
+
// Guard: no token means the user hit this page directly or followed a
|
|
35
|
+
// malformed/expired link. Don't render a form that can't succeed.
|
|
36
|
+
if (!token) {
|
|
37
|
+
return (_jsxs("div", { className: `w-full max-w-md mx-auto p-6 ${className}`, children: [_jsx("h2", { className: "text-2xl font-bold mb-6", children: "Reset Password" }), _jsx("div", { className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded", children: "This password reset link is invalid or has expired. Please request a new one." })] }));
|
|
38
|
+
}
|
|
39
|
+
const handleSubmit = async (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
setError('');
|
|
42
|
+
if (requirePasswordConfirmation && password !== confirmPassword) {
|
|
43
|
+
setError('Passwords do not match');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setLoading(true);
|
|
47
|
+
try {
|
|
48
|
+
const result = await resetPasswordAction({ token, password });
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
throw new Error(cleanAuthErrorMessage(result.error, 'Failed to reset password'));
|
|
51
|
+
}
|
|
52
|
+
if (onSuccess) {
|
|
53
|
+
onSuccess();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
router.push(redirectTo);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const message = cleanAuthErrorMessage(err instanceof Error ? err.message : undefined, 'Failed to reset password');
|
|
61
|
+
setError(message);
|
|
62
|
+
onError?.(err instanceof Error ? err : new Error(message));
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return (_jsxs("div", { className: `w-full max-w-md mx-auto p-6 ${className}`, children: [_jsx("h2", { className: "text-2xl font-bold mb-6", children: "Reset Password" }), error && (_jsx("div", { className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4", children: error })), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "password", className: "block text-sm font-medium mb-2", children: "New Password" }), _jsx("input", { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] }), requirePasswordConfirmation && (_jsxs("div", { children: [_jsx("label", { htmlFor: "confirmPassword", className: "block text-sm font-medium mb-2", children: "Confirm New Password" }), _jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] })), _jsx("button", { type: "submit", disabled: loading, className: "w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed", children: loading ? 'Resetting...' : 'Reset Password' })] })] }));
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ResetPasswordForm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResetPasswordForm.js","sourceRoot":"","sources":["../../../src/ui/components/ResetPasswordForm.tsx"],"names":[],"mappings":"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"}
|