Skip to content

Commit 0a3d443

Browse files
committed
feat: password reset url is configurable
1 parent 3139eab commit 0a3d443

File tree

16 files changed

+111
-39
lines changed

16 files changed

+111
-39
lines changed

docs/api/types.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ interface RuntimeModuleOptions {
126126
passwordResetTokens?: string
127127
}
128128
mailer?: MailerOptions
129-
passwordResetBaseUrl?: string
129+
passwordResetUrl?: string
130130
auth?: {
131131
whitelist?: string[]
132132
tokenExpiration?: number
@@ -161,7 +161,7 @@ interface ModuleOptions {
161161
passwordResetTokens: string
162162
}
163163
mailer?: MailerOptions
164-
passwordResetBaseUrl?: string
164+
passwordResetUrl: string
165165
auth: {
166166
whitelist: string[]
167167
tokenExpiration: number

docs/examples/advanced-configuration.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export default defineNuxtConfig({
3939
}
4040
},
4141

42-
// Production URLs
43-
passwordResetBaseUrl: process.env.PASSWORD_RESET_BASE_URL || 'https://myapp.com',
42+
// Password reset URL path
43+
passwordResetUrl: process.env.PASSWORD_RESET_URL || '/reset-password',
4444

4545
// Security settings
4646
auth: {

docs/user-guide/configuration.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export default defineNuxtConfig({
5959
}
6060
},
6161

62-
// Password reset URL
63-
passwordResetBaseUrl: 'https://yourapp.com',
62+
// Password reset URL path
63+
passwordResetUrl: '/reset-password',
6464

6565
// Authentication settings
6666
auth: {
@@ -159,7 +159,7 @@ nuxtUsers: {
159159
from: '"My App" <noreply@example.com>'
160160
}
161161
},
162-
passwordResetBaseUrl: 'http://localhost:3000'
162+
passwordResetUrl: '/reset-password'
163163
}
164164
```
165165

@@ -181,7 +181,7 @@ nuxtUsers: {
181181
from: '"Your App" <noreply@yourapp.com>'
182182
}
183183
},
184-
passwordResetBaseUrl: 'https://yourapp.com'
184+
passwordResetUrl: '/reset-password'
185185
}
186186
```
187187

@@ -248,7 +248,7 @@ nuxtUsers: {
248248
}
249249
```
250250

251-
**Note:** Login (`/login`) and password reset routes are always whitelisted automatically.
251+
**Note:** Login (`/login`), password reset page (default `/reset-password` or your custom `passwordResetUrl`), and password reset API endpoints are always whitelisted automatically.
252252

253253
### Role-Based Access Control (RBAC)
254254

@@ -397,7 +397,7 @@ export default defineNuxtConfig({
397397
}
398398
},
399399

400-
passwordResetBaseUrl: process.env.PASSWORD_RESET_BASE_URL,
400+
passwordResetUrl: process.env.PASSWORD_RESET_URL || '/reset-password',
401401

402402
auth: {
403403
tokenExpiration: Number(process.env.TOKEN_EXPIRATION) || 1440
@@ -428,7 +428,7 @@ MAILER_PASS=app_specific_password
428428
MAILER_FROM="My App <noreply@myapp.com>"
429429

430430
# URLs
431-
PASSWORD_RESET_BASE_URL=https://myapp.com
431+
PASSWORD_RESET_URL=/auth/reset-password
432432

433433
# Auth
434434
TOKEN_EXPIRATION=1440
@@ -474,7 +474,7 @@ const defaults = {
474474
from: '"Nuxt Users Module" <noreply@example.com>'
475475
}
476476
},
477-
passwordResetBaseUrl: 'http://localhost:3000',
477+
passwordResetUrl: '/reset-password',
478478
auth: {
479479
whitelist: [],
480480
tokenExpiration: 1440,

docs/user-guide/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ export default defineNuxtConfig({
5858
// from: '"Your App Name" <noreply@example.com>',
5959
// },
6060
// },
61-
// Optional: Configure base URL for password reset links
62-
// passwordResetBaseUrl: 'http://localhost:3000', // Default
61+
// Optional: Configure URL path for password reset page
62+
// passwordResetUrl: '/reset-password', // Default
6363
}
6464
})
6565
```

docs/user-guide/password-reset.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,52 @@ https://yourapp.com/reset-password?token=abc123...&email=user@example.com
3030

3131
Your reset password page should read these query parameters to populate the reset form.
3232

33+
## Reset URL Configuration
34+
35+
The module automatically constructs password reset URLs using your application's base URL and the configured path.
36+
37+
### Default Behavior
38+
39+
By default, reset links use `/reset-password` as the path:
40+
41+
```
42+
https://yourapp.com/reset-password?token=abc123...&email=user@example.com
43+
```
44+
45+
### Custom Reset URL Path
46+
47+
You can customize the path portion of the reset URL:
48+
49+
```ts
50+
nuxtUsers: {
51+
passwordResetUrl: '/auth/reset-password', // Custom path
52+
}
53+
```
54+
55+
This will generate reset links like:
56+
57+
```
58+
https://yourapp.com/auth/reset-password?token=abc123...&email=user@example.com
59+
```
60+
61+
### Zero Configuration
62+
63+
The module automatically detects your application's base URL from the incoming request, so you don't need to configure a full base URL. The system intelligently combines:
64+
65+
- **Request host**: Automatically detected from headers
66+
- **Protocol**: Detected from `x-forwarded-proto` header or defaults to `http`
67+
- **Reset path**: Your configured `passwordResetUrl` (default: `/reset-password`)
68+
69+
### Development vs Production
70+
71+
**Development** (automatic detection):
72+
- Request to `http://localhost:3000` → Reset URL: `http://localhost:3000/reset-password`
73+
74+
**Production** (automatic detection):
75+
- Request to `https://myapp.com` → Reset URL: `https://myapp.com/reset-password`
76+
77+
No manual configuration of base URLs required!
78+
3379
## Configuration
3480

3581
Configure the mailer in your `nuxt.config.ts`:
@@ -57,7 +103,7 @@ export default defineNuxtConfig({
57103
from: '"Your App" <noreply@yourapp.com>',
58104
},
59105
},
60-
passwordResetBaseUrl: 'https://yourapp.com', // Base URL for reset links
106+
passwordResetUrl: '/reset-password', // URL path for password reset page
61107
}
62108
})
63109
```
@@ -81,7 +127,7 @@ nuxtUsers: {
81127
from: '"My Nuxt App" <noreply@example.com>',
82128
},
83129
},
84-
passwordResetBaseUrl: 'http://localhost:3000',
130+
passwordResetUrl: '/reset-password', // Custom URL path (optional)
85131
}
86132
```
87133

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"lint": "eslint .",
6767
"lint:fix": "eslint . --fix",
6868
"pretest": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for testing' && exit 1)",
69-
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
69+
"test:types": "yarn dev:prepare && vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
7070
"test:unit": "./scripts/test-unit.sh",
7171
"test:sqlite": "./scripts/test-sqlite.sh",
7272
"test:mysql": "./scripts/test-mysql.sh",

src/module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const defaultOptions: ModuleOptions = {
2828
from: '"Nuxt Users Module" <noreply@example.com>',
2929
},
3030
},
31-
passwordResetBaseUrl: 'http://localhost:3000',
31+
passwordResetUrl: '/reset-password',
3232
auth: {
3333
whitelist: [],
3434
tokenExpiration: 24 * 60, // 24 hours
@@ -61,6 +61,7 @@ export default defineNuxtModule<RuntimeModuleOptions>({
6161
nuxt.options.runtimeConfig.nuxtUsers = {
6262
...runtimeConfigOptions,
6363
apiBasePath: options.apiBasePath || defaultOptions.apiBasePath,
64+
passwordResetUrl: options.passwordResetUrl || defaultOptions.passwordResetUrl,
6465
tables: {
6566
migrations: options.tables?.migrations || defaultOptions.tables.migrations,
6667
users: options.tables?.users || defaultOptions.tables.users,

src/runtime/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Page routes that don't require authentication
2-
export const NO_AUTH_PATHS = ['/login']
2+
export const NO_AUTH_PATHS = ['/login', '/reset-password']
33

44
// API routes that don't require authentication (will be prefixed with apiBasePath)
55
export const NO_AUTH_API_PATHS = ['/session', '/password/forgot', '/password/reset']

src/runtime/server/api/nuxt-users/password/forgot.post.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineEventHandler, readBody, createError } from 'h3'
1+
import { defineEventHandler, readBody, createError, getHeader } from 'h3'
22
import { sendPasswordResetLink } from '../../../services/password'
33
import { useRuntimeConfig } from '#imports'
44
import type { ModuleOptions } from 'nuxt-users/utils'
@@ -18,7 +18,12 @@ export default defineEventHandler(async (event) => {
1818
}
1919

2020
try {
21-
await sendPasswordResetLink(email, options)
21+
// Try to determine the base URL from the request
22+
const host = getHeader(event, 'host')
23+
const protocol = getHeader(event, 'x-forwarded-proto') || 'http'
24+
const baseUrl = host ? `${protocol}://${host}` : undefined
25+
26+
await sendPasswordResetLink(email, options, baseUrl)
2227
// Always return a success-like message to prevent email enumeration
2328
return { message: 'If a user with that email exists, a password reset link has been sent.' }
2429
}

src/runtime/server/middleware/authorization.server.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ export default defineEventHandler(async (event) => {
2020
return
2121
}
2222

23-
// internal no-auth paths (e.g., /login)
24-
if (NO_AUTH_PATHS.includes(event.path)) {
23+
// internal no-auth paths (e.g., /login, /reset-password) and custom password reset URL
24+
const noAuthPaths = [...NO_AUTH_PATHS]
25+
// Add custom password reset URL if different from default
26+
if (options.passwordResetUrl && options.passwordResetUrl !== '/reset-password') {
27+
noAuthPaths.push(options.passwordResetUrl)
28+
}
29+
30+
if (noAuthPaths.includes(event.path)) {
2531
console.log(`[Nuxt Users] server.middleware.auth.global: NO_AUTH_PATH: ${event.path}`)
2632
return
2733
}

0 commit comments

Comments
 (0)