@nxgt/janus-mail 0.5.1 → 0.6.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/README.md +34 -16
- package/docs/README.md +1 -1
- package/docs/guide/sending.md +35 -9
- package/docs/roadmap.md +7 -0
- package/docs/troubleshooting.md +17 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ bun add -d typescript
|
|
|
47
47
|
|
|
48
48
|
Three peers, all required: `@nxgt/mail` (0.1 or later, below 2 — 1.0 and
|
|
49
49
|
its transports included), which defines the `Mailer` port and the errors;
|
|
50
|
-
`@nxgt/janus` (0.
|
|
50
|
+
`@nxgt/janus` (0.14), whose flows' answers the methods take — types only,
|
|
51
51
|
nothing of it is loaded; and `typescript` (6). **No Maizzle, no Vue, no
|
|
52
52
|
Tailwind**: they run at this package's build, not in yours.
|
|
53
53
|
|
|
@@ -144,21 +144,35 @@ if (issued !== null) {
|
|
|
144
144
|
await mail.verifyEmail(issued, { name: user.name, locale: 'fr' }, { expiresIn: '24 heures' });
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
### Telling a user their password changed
|
|
147
|
+
### Telling a user their password or e-mail changed
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
### Telling a user their e-mail changed
|
|
149
|
+
`@nxgt/janus` 0.14 sends `user.passwordChanged` once `changePassword` or
|
|
150
|
+
`setPassword` wrote the password, and `user.emailChanged` once `update`
|
|
151
|
+
changed the e-mail. The e-mail change is told to the **former** address — the
|
|
152
|
+
new one belongs to whoever changed it — which the event carries as
|
|
153
|
+
`formerEmail`, `null` for a user who had none:
|
|
155
154
|
|
|
156
155
|
```ts
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
const auth = janus({
|
|
157
|
+
...config,
|
|
158
|
+
async events(event) {
|
|
159
|
+
if (event.type === 'user.passwordChanged') {
|
|
160
|
+
const user = await auth.get(event.userId);
|
|
161
|
+
await mail.passwordChanged({ name: user.name, locale: user.locale, email: user.email });
|
|
162
|
+
}
|
|
163
|
+
if (event.type === 'user.emailChanged' && event.formerEmail != null) {
|
|
164
|
+
const user = await auth.get(event.userId);
|
|
165
|
+
await mail.emailChanged({ name: user.name, locale: user.locale, formerEmail: event.formerEmail, newEmail: user.email });
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
|
160
169
|
```
|
|
161
170
|
|
|
171
|
+
A reset sends `user.passwordReset`, not `user.passwordChanged`: add that type
|
|
172
|
+
to the first test to tell a reset too. The examples assume a required e-mail;
|
|
173
|
+
with an optional one, an update that removes it has no `newEmail` to name —
|
|
174
|
+
see [Sending](docs/guide/sending.md#emailchangedto).
|
|
175
|
+
|
|
162
176
|
### Telling a user their second factor was turned on or off
|
|
163
177
|
|
|
164
178
|
`@nxgt/janus` sends `user.secondFactorEnabled` once `secondFactor.activate`
|
|
@@ -380,8 +394,8 @@ The symptoms and fixes are in [troubleshooting](docs/troubleshooting.md).
|
|
|
380
394
|
|
|
381
395
|
## Type safety, counted
|
|
382
396
|
|
|
383
|
-
**Thirty-
|
|
384
|
-
|
|
397
|
+
**Thirty-six plausible mistakes, thirty-six refused at compile time.**
|
|
398
|
+
Six files hold one `@ts-expect-error` per mistake, beside the calls that
|
|
385
399
|
must keep compiling:
|
|
386
400
|
[`test/types/send-refusals.ts`](test/types/send-refusals.ts) the eight of a
|
|
387
401
|
send, 1 to 8;
|
|
@@ -391,9 +405,11 @@ template;
|
|
|
391
405
|
[`test/types/expiry-refusals.ts`](test/types/expiry-refusals.ts) the five of
|
|
392
406
|
the expiry and the clock, 21 to 25;
|
|
393
407
|
[`test/types/notice-refusals.ts`](test/types/notice-refusals.ts) the four of
|
|
394
|
-
the two-factor notices and the welcome, 26 to 29;
|
|
408
|
+
the two-factor notices and the welcome, 26 to 29;
|
|
395
409
|
[`test/types/recovery-refusals.ts`](test/types/recovery-refusals.ts) the
|
|
396
|
-
five of the recovery code notice, 30 to 34
|
|
410
|
+
five of the recovery code notice, 30 to 34; and
|
|
411
|
+
[`test/types/change-refusals.ts`](test/types/change-refusals.ts) the two of
|
|
412
|
+
the e-mail change notice sent on its event, 35 and 36:
|
|
397
413
|
|
|
398
414
|
1. A sign-in code given to `verifyEmail`: it has no token.
|
|
399
415
|
2. A one-time token given to `signInCode`: it has no code.
|
|
@@ -429,8 +445,10 @@ five of the recovery code notice, 30 to 34:
|
|
|
429
445
|
32. The count as `recoveryCodesLeft()` answered it, not checked for `null` first.
|
|
430
446
|
33. `when` given as the event's `Date` rather than the text in the recipient's locale and time zone.
|
|
431
447
|
34. `links.recoveryCodes` given as a URL rather than a function.
|
|
448
|
+
35. `user.emailChanged`'s `formerEmail` given to `emailChanged` unchecked: it is `null` for a user who had none.
|
|
449
|
+
36. The `user.emailChanged` event itself given to `emailChanged`: it has the former address, but neither a name nor the new one.
|
|
432
450
|
|
|
433
|
-
In JavaScript, 19 to 23, 26 to 28
|
|
451
|
+
In JavaScript, 19 to 23, 26 to 28, 30 to 33, 35 and 36 are a `TypeError` at send
|
|
434
452
|
time instead, naming the call or the field, and 25, 29 and 34 one from
|
|
435
453
|
`janusMail()`.
|
|
436
454
|
|
package/docs/README.md
CHANGED
|
@@ -5,7 +5,7 @@ the detail.
|
|
|
5
5
|
|
|
6
6
|
| Page | Read it when |
|
|
7
7
|
| --- | --- |
|
|
8
|
-
| [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the welcome on `user.created
|
|
8
|
+
| [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the welcome on `user.created`, the recovery code notice on `user.recoveryCodeUsed` and the change notices on `user.passwordChanged` and `user.emailChanged` among them; the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, retrying and tracing the mailer, and a test |
|
|
9
9
|
| [Templates](guide/templates.md) | You want an e-mail of your own: what a template takes and answers, a partial override, `janusTemplates()`, and what the defaults say |
|
|
10
10
|
| [Locales](guide/locales.md) | You send in more than one language: how the locale is picked, `fallbackLocale`, fewer locales, and adding one |
|
|
11
11
|
| [Building](guide/building.md) | You work on this package: the Maizzle project in `mail/`, `mails/`, the generated files, and why none of it reaches a consumer's build |
|
package/docs/guide/sending.md
CHANGED
|
@@ -117,26 +117,52 @@ name — and only its `locale` is read.
|
|
|
117
117
|
### `passwordChanged(to)`
|
|
118
118
|
|
|
119
119
|
```ts
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
// On the user event @nxgt/janus 0.14 sends once the password is written:
|
|
121
|
+
const auth = janus({
|
|
122
|
+
...config,
|
|
123
|
+
async events(event) {
|
|
124
|
+
if (event.type !== 'user.passwordChanged') return;
|
|
125
|
+
const user = await auth.get(event.userId);
|
|
126
|
+
await mail.passwordChanged({ name: user.name, locale: user.locale, email: user.email });
|
|
127
|
+
},
|
|
128
|
+
});
|
|
122
129
|
```
|
|
123
130
|
|
|
124
131
|
A notice: the password of the account at `to.email` changed, with a link to
|
|
125
|
-
`links.secureAccount()` for a user who did not change it. Send it
|
|
126
|
-
`
|
|
132
|
+
`links.secureAccount()` for a user who did not change it. Send it on the
|
|
133
|
+
`user.passwordChanged` event, which `changePassword` and `setPassword` send.
|
|
134
|
+
A reset sends `user.passwordReset` instead, never both: to tell a reset too,
|
|
135
|
+
send the same notice on that type — `event.type === 'user.passwordChanged'
|
|
136
|
+
|| event.type === 'user.passwordReset'`. The event names the user by id
|
|
137
|
+
alone, so read the name, the locale and the address from the user.
|
|
138
|
+
`setPassword` sends it for a user's first password too — an account created
|
|
139
|
+
without one — so the notice then tells them a password was set on their
|
|
140
|
+
account, which is worth knowing all the same.
|
|
127
141
|
|
|
128
142
|
### `emailChanged(to)`
|
|
129
143
|
|
|
130
144
|
```ts
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
145
|
+
// On the user event @nxgt/janus 0.14 sends when an update changed the e-mail:
|
|
146
|
+
const auth = janus({
|
|
147
|
+
...config,
|
|
148
|
+
async events(event) {
|
|
149
|
+
if (event.type !== 'user.emailChanged' || event.formerEmail == null) return;
|
|
150
|
+
const user = await auth.get(event.userId);
|
|
151
|
+
await mail.emailChanged({ name: user.name, locale: user.locale, formerEmail: event.formerEmail, newEmail: user.email });
|
|
152
|
+
},
|
|
153
|
+
});
|
|
134
154
|
```
|
|
135
155
|
|
|
136
156
|
Sent to **`formerEmail`**: the owner of the old address is the one to warn,
|
|
137
157
|
since whoever changed it already controls the new one. It names `newEmail`,
|
|
138
|
-
and links to `links.secureAccount()`.
|
|
139
|
-
|
|
158
|
+
and links to `links.secureAccount()`. The `user.emailChanged` event is the
|
|
159
|
+
one that carries an address — `event.formerEmail`, the e-mail before the
|
|
160
|
+
update — since the user read back already holds the new one. It is `null`
|
|
161
|
+
for a user who had no e-mail before: there is no former inbox to tell, and
|
|
162
|
+
the type refuses it unchecked. The example assumes a required e-mail: with
|
|
163
|
+
an optional one, `update` also sends the event when it removes the address,
|
|
164
|
+
and the user read back has no `newEmail` to name — check `user.email` first,
|
|
165
|
+
and tell the former address your own way or not at all.
|
|
140
166
|
|
|
141
167
|
### `twoFactorEnabled(to)` and `twoFactorDisabled(to)`
|
|
142
168
|
|
package/docs/roadmap.md
CHANGED
|
@@ -47,6 +47,13 @@ Nothing between releases.
|
|
|
47
47
|
|
|
48
48
|
Newest first; from the first release on, the package's CHANGELOG holds every one.
|
|
49
49
|
|
|
50
|
+
- **The change notices on their events, v0.6.0.** `passwordChanged` and
|
|
51
|
+
`emailChanged` are sent from `@nxgt/janus` 0.14's `user.passwordChanged`
|
|
52
|
+
and `user.emailChanged` events, as the two-factor notices are, rather than
|
|
53
|
+
after the call: whoever changed the password or the e-mail, the user is
|
|
54
|
+
told. `emailChanged` goes to the event's `formerEmail`, the inbox the
|
|
55
|
+
account just left, and the compiler refuses it unchecked for `null`.
|
|
56
|
+
|
|
50
57
|
- **The recovery code notice, and a readable link in both modes, v0.5.0.**
|
|
51
58
|
`recoveryCodeUsed(to, { when, recoveryCodesLeft })`, built from
|
|
52
59
|
`@nxgt/mail-presets` 1.0.0's `recovery-code-used` in English and French:
|
package/docs/troubleshooting.md
CHANGED
|
@@ -255,14 +255,29 @@ number of seconds: pass the text to show, `{ expiresIn: '1 hour' }`, or
|
|
|
255
255
|
leave it out.
|
|
256
256
|
|
|
257
257
|
`janusMail.twoFactorEnabled: email must be a string` (or `twoFactorDisabled`,
|
|
258
|
-
`recoveryCodeUsed` or `welcome`) is the user event itself
|
|
259
|
-
e-mail: an event names the user by id alone. Read the user first:
|
|
258
|
+
`passwordChanged`, `recoveryCodeUsed` or `welcome`) is the user event itself
|
|
259
|
+
given to the e-mail: an event names the user by id alone. Read the user first:
|
|
260
260
|
|
|
261
261
|
```ts
|
|
262
262
|
const user = await auth.get(event.userId);
|
|
263
263
|
await mail.twoFactorDisabled({ name: user.name, locale: user.locale, email: user.email });
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
On `user.emailChanged`, `janusMail.emailChanged: name must be a string` is
|
|
267
|
+
the event itself given to `emailChanged`, and `janusMail.emailChanged:
|
|
268
|
+
formerEmail must be a string` is `event.formerEmail` passed without its
|
|
269
|
+
check — it is `null` for a user who had no e-mail before. `janusMail.emailChanged:
|
|
270
|
+
newEmail must be a string` is an update that removed the e-mail: there is no
|
|
271
|
+
new address to name. Check both, and read the rest from the user:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
if (event.type === 'user.emailChanged' && event.formerEmail != null) {
|
|
275
|
+
const user = await auth.get(event.userId);
|
|
276
|
+
if (user.email === undefined) return; // removed: tell the former address your own way
|
|
277
|
+
await mail.emailChanged({ name: user.name, locale: user.locale, formerEmail: event.formerEmail, newEmail: user.email });
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
266
281
|
`janusMail.recoveryCodeUsed: when must be a string` is the second argument
|
|
267
282
|
missing, or its `when` given as the event's `Date`: pass the text to show,
|
|
268
283
|
formatted in the recipient's locale and time zone —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-mail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The e-mails of @nxgt/janus's flows — e-mail verification, password reset, sign-in code, password changed, e-mail changed, two-factor enabled and disabled, recovery code used, welcome — rendered in English and French from a prebuilt Maizzle build, with your brand, sent through any @nxgt/mail transport",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@maizzle/framework": "6.1.7",
|
|
55
55
|
"@maizzle/tailwindcss": "1.5.6",
|
|
56
56
|
"@formatjs/icu-messageformat-parser": "3.5.20",
|
|
57
|
-
"@nxgt/janus": "^0.
|
|
57
|
+
"@nxgt/janus": "^0.14.0",
|
|
58
58
|
"@nxgt/mail": "1.0.1",
|
|
59
59
|
"@nxgt/mail-config": "1.0.1",
|
|
60
60
|
"@nxgt/mail-i18n": "1.0.1",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"zod": "^4.6.5"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@nxgt/janus": "^0.
|
|
69
|
+
"@nxgt/janus": "^0.14.0",
|
|
70
70
|
"@nxgt/mail": ">=0.1.0 <2",
|
|
71
71
|
"typescript": "^6.0.3"
|
|
72
72
|
}
|