mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into llms-improvement
This commit is contained in:
17
packages/accounts-base/accounts-base.d.ts
vendored
17
packages/accounts-base/accounts-base.d.ts
vendored
@@ -3,11 +3,20 @@ import { Meteor } from 'meteor/meteor';
|
||||
import { Configuration } from 'meteor/service-configuration';
|
||||
import { DDP } from 'meteor/ddp';
|
||||
|
||||
/**
|
||||
* Object containing functions that generate URLs for account-related emails.
|
||||
* Override these to customize URLs in password reset, enrollment, and verification emails.
|
||||
* URL methods can return either a string or a Promise that resolves to a string.
|
||||
*/
|
||||
export interface URLS {
|
||||
resetPassword: (token: string) => string;
|
||||
verifyEmail: (token: string) => string;
|
||||
loginToken: (token: string) => string;
|
||||
enrollAccount: (token: string) => string;
|
||||
/** Generates the URL for password reset emails. Can return a Promise for async URL generation. */
|
||||
resetPassword: (token: string, extraParams?: Record<string, string>) => string | Promise<string>;
|
||||
/** Generates the URL for email verification emails. Can return a Promise for async URL generation. */
|
||||
verifyEmail: (token: string, extraParams?: Record<string, string>) => string | Promise<string>;
|
||||
/** Generates the URL for login token emails. Can return a Promise for async URL generation. */
|
||||
loginToken: (selector: string, token: string, extraParams?: Record<string, string>) => string | Promise<string>;
|
||||
/** Generates the URL for account enrollment emails. Can return a Promise for async URL generation. */
|
||||
enrollAccount: (token: string, extraParams?: Record<string, string>) => string | Promise<string>;
|
||||
}
|
||||
|
||||
export interface EmailFields {
|
||||
|
||||
@@ -83,6 +83,25 @@ export class AccountsServer extends AccountsCommon {
|
||||
return Meteor._isPromise(value) ? await value : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Object containing functions that generate URLs for account-related emails.
|
||||
* Override these to customize URLs in emails sent by
|
||||
* [`Accounts.sendResetPasswordEmail`](#Accounts-sendResetPasswordEmail),
|
||||
* [`Accounts.sendEnrollmentEmail`](#Accounts-sendEnrollmentEmail), and
|
||||
* [`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail).
|
||||
*
|
||||
* By default, URLs use hash fragments (e.g., `#/reset-password/:token`) for security:
|
||||
* hash fragments are not sent to the server in HTTP requests, preventing tokens from
|
||||
* appearing in server logs or referrer headers.
|
||||
* @locus Server
|
||||
* @memberof Accounts
|
||||
* @name urls
|
||||
* @type {Object}
|
||||
* @property {Function} resetPassword - `(token, extraParams) => string` - Generates password reset URL.
|
||||
* @property {Function} verifyEmail - `(token, extraParams) => string` - Generates email verification URL.
|
||||
* @property {Function} enrollAccount - `(token, extraParams) => string` - Generates account enrollment URL.
|
||||
* @property {Function} loginToken - `(selector, token, extraParams) => string` - Generates login token URL.
|
||||
*/
|
||||
this.urls = {
|
||||
resetPassword: (token, extraParams) => this.buildEmailUrl(`#/reset-password/${token}`, extraParams),
|
||||
verifyEmail: (token, extraParams) => this.buildEmailUrl(`#/verify-email/${token}`, extraParams),
|
||||
@@ -93,6 +112,16 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
this.addDefaultRateLimit();
|
||||
|
||||
/**
|
||||
* @summary Builds a URL for account-related emails by combining the app's
|
||||
* root URL with a path and optional extra parameters.
|
||||
* @locus Server
|
||||
* @memberof Accounts
|
||||
* @name buildEmailUrl
|
||||
* @param {String} path - The path to append to the root URL (e.g., `#/reset-password/TOKEN`).
|
||||
* @param {Object} [extraParams={}] - Additional query parameters to include in the URL.
|
||||
* @returns {String} The complete URL.
|
||||
*/
|
||||
this.buildEmailUrl = (path, extraParams = {}) => {
|
||||
const url = new URL(Meteor.absoluteUrl(path));
|
||||
const params = Object.entries(extraParams);
|
||||
|
||||
@@ -991,12 +991,173 @@ be called.
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#Accounts-emailTemplates).
|
||||
|
||||
## Email Link Callbacks and URL Customization
|
||||
|
||||
When Meteor sends account-related emails, those emails contain URLs that users click
|
||||
to complete actions like password reset. This section explains how these URLs work
|
||||
and how to customize them.
|
||||
|
||||
### How Email URLs Work
|
||||
|
||||
By default, Meteor generates URLs using hash fragments:
|
||||
|
||||
- `https://yourapp.com/#/reset-password/TOKEN`
|
||||
- `https://yourapp.com/#/verify-email/TOKEN`
|
||||
- `https://yourapp.com/#/enroll-account/TOKEN`
|
||||
|
||||
**Security Note:** Hash fragments (the part after `#`) are intentionally used because
|
||||
they are never sent to the server in HTTP requests. This prevents sensitive tokens
|
||||
from appearing in server logs, proxy logs, or HTTP referrer headers.
|
||||
|
||||
When a user clicks these links, Meteor's client-side code automatically parses
|
||||
`window.location.hash` and triggers the appropriate callback registered with
|
||||
the functions below.
|
||||
|
||||
<ApiBox name="Accounts.onResetPasswordLink" />
|
||||
|
||||
<ApiBox name="Accounts.onEnrollmentLink" />
|
||||
|
||||
<ApiBox name="Accounts.onEmailVerificationLink" />
|
||||
|
||||
### Complete Example: Custom Password Reset Flow
|
||||
|
||||
Here's how to implement password reset without `accounts-ui`:
|
||||
|
||||
```js
|
||||
// client/accounts-hooks.js
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
// Register at top level, NOT inside Meteor.startup()
|
||||
let doneCallback;
|
||||
|
||||
Accounts.onResetPasswordLink((token, done) => {
|
||||
// Store token and done callback for your UI
|
||||
Session.set('resetPasswordToken', token);
|
||||
doneCallback = done;
|
||||
|
||||
// Show your password reset form
|
||||
// The login process is suspended until done() is called
|
||||
});
|
||||
|
||||
// In your password reset form submit handler:
|
||||
function submitNewPassword(newPassword) {
|
||||
const token = Session.get('resetPasswordToken');
|
||||
|
||||
Accounts.resetPassword(token, newPassword, (error) => {
|
||||
if (error) {
|
||||
alert('Reset failed: ' + error.reason);
|
||||
} else {
|
||||
Session.set('resetPasswordToken', null);
|
||||
doneCallback(); // Re-enables auto-login
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing Email URLs
|
||||
|
||||
<ApiBox name="Accounts.urls" />
|
||||
|
||||
`Accounts.urls` is a server-side object containing functions that generate URLs
|
||||
for account emails. Override these to customize the URL format.
|
||||
|
||||
| Property | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `resetPassword` | `(token, extraParams?) => string` | Password reset URL |
|
||||
| `verifyEmail` | `(token, extraParams?) => string` | Email verification URL |
|
||||
| `enrollAccount` | `(token, extraParams?) => string` | Account enrollment URL |
|
||||
| `loginToken` | `(selector, token, extraParams?) => string` | Login token URL |
|
||||
|
||||
#### Async URL Generation
|
||||
|
||||
The URL methods can also return **Promises** that resolve to strings. This is useful when
|
||||
URL generation requires asynchronous operations, such as:
|
||||
- Looking up user data from the database
|
||||
- Calling external services (e.g., URL shorteners)
|
||||
- Generating signed URLs from cloud providers
|
||||
|
||||
The email-sending functions (`Accounts.sendResetPasswordEmail`, `Accounts.sendEnrollmentEmail`,
|
||||
and `Accounts.sendVerificationEmail`) handle both synchronous and asynchronous URL methods
|
||||
transparently.
|
||||
|
||||
**Example: Async URL with database lookup**
|
||||
|
||||
```js
|
||||
// Server-side
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Accounts.urls.resetPassword = async (token, extraParams) => {
|
||||
// Example: Look up user preference for custom domain
|
||||
const user = await Meteor.users.findOneAsync({ 'services.password.reset.token': token });
|
||||
const domain = user?.profile?.preferredDomain || Meteor.absoluteUrl();
|
||||
|
||||
return `${domain}reset-password/${token}`;
|
||||
};
|
||||
```
|
||||
|
||||
**Example: Using a URL shortener service**
|
||||
|
||||
```js
|
||||
// Server-side
|
||||
Accounts.urls.verifyEmail = async (token) => {
|
||||
const longUrl = Meteor.absoluteUrl(`verify-email/${token}`);
|
||||
|
||||
// Shorten the URL using an external service
|
||||
const shortUrl = await shortenUrl(longUrl);
|
||||
return shortUrl;
|
||||
};
|
||||
```
|
||||
|
||||
**Example: Using Clean URLs Instead of Hash Fragments**
|
||||
|
||||
If your router doesn't handle hash fragments well, you can override `Accounts.urls`
|
||||
to use clean URLs:
|
||||
|
||||
```js
|
||||
// Server-side
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Accounts.urls.resetPassword = (token) => {
|
||||
return Meteor.absoluteUrl(`reset-password/${token}`);
|
||||
};
|
||||
|
||||
Accounts.urls.verifyEmail = (token) => {
|
||||
return Meteor.absoluteUrl(`verify-email/${token}`);
|
||||
};
|
||||
|
||||
Accounts.urls.enrollAccount = (token) => {
|
||||
return Meteor.absoluteUrl(`enroll-account/${token}`);
|
||||
};
|
||||
```
|
||||
|
||||
**Important:** When using clean URLs (without `#/`), the built-in
|
||||
`Accounts.onResetPasswordLink`, `Accounts.onEnrollmentLink`, and
|
||||
`Accounts.onEmailVerificationLink` callbacks won't work automatically.
|
||||
Handle tokens in your router instead:
|
||||
|
||||
```js
|
||||
// Example with a router
|
||||
Router.route('/reset-password/:token', function() {
|
||||
const token = this.params.token;
|
||||
// Show password reset UI, call Accounts.resetPassword(token, newPassword)
|
||||
});
|
||||
```
|
||||
|
||||
### Router Integration
|
||||
|
||||
You have three options when integrating with client-side routers:
|
||||
|
||||
1. **Keep default hash URLs** - Works out of the box
|
||||
with `Accounts.on*Link` callbacks. No router configuration needed.
|
||||
|
||||
2. **Override `Accounts.urls` for clean URLs** - More "modern" looking URLs,
|
||||
but requires handling tokens in your router.
|
||||
|
||||
3. **Use hashbang mode** - Some routers support `#!/` routes. Configure your
|
||||
router accordingly and update `Accounts.urls` to use `#!/` instead of `#/`.
|
||||
|
||||
<ApiBox name="Accounts.emailTemplates" />
|
||||
|
||||
This is an `Object` with several fields that are used to generate text/html
|
||||
|
||||
Reference in New Issue
Block a user