Merge branch 'devel' into llms-improvement

This commit is contained in:
Harry Adel
2026-03-02 23:09:40 +02:00
committed by GitHub
3 changed files with 203 additions and 4 deletions

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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