@nxgt/janus-mail 0.7.1 → 0.9.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 +104 -18
- package/dist/generated/mail.d.ts +15 -0
- package/dist/generated/mail.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -16
- package/dist/index.js.map +8 -6
- package/dist/janus-mail.d.ts.map +1 -1
- package/dist/link-types.d.ts +51 -0
- package/dist/link-types.d.ts.map +1 -0
- package/dist/new-sign-in-mail.d.ts +16 -0
- package/dist/new-sign-in-mail.d.ts.map +1 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/step-up-mail.d.ts +5 -0
- package/dist/step-up-mail.d.ts.map +1 -0
- package/dist/template-types.d.ts +26 -2
- package/dist/template-types.d.ts.map +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/types.d.ts +33 -47
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +4 -4
- package/docs/guide/building.md +1 -1
- package/docs/guide/locales.md +10 -5
- package/docs/guide/sending.md +91 -5
- package/docs/guide/templates.md +32 -18
- package/docs/roadmap.md +24 -25
- package/docs/troubleshooting.md +100 -14
- package/mails/en/confirm-action.html +52 -0
- package/mails/en/confirm-action.txt +25 -0
- package/mails/en/new-sign-in.html +60 -0
- package/mails/en/new-sign-in.txt +30 -0
- package/mails/fr/confirm-action.html +52 -0
- package/mails/fr/confirm-action.txt +25 -0
- package/mails/fr/new-sign-in.html +58 -0
- package/mails/fr/new-sign-in.txt +30 -0
- package/mails/mail-manifest.json +53 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
The e-mails of [`@nxgt/janus`](https://www.npmjs.com/package/@nxgt/janus)'s
|
|
4
4
|
flows, ready to send: **e-mail verification, password reset, sign-in code,
|
|
5
|
-
sign-in link, password changed, e-mail changed, two-factor authentication turned on or
|
|
6
|
-
off, a recovery code used, and a welcome to a new user**, in English and
|
|
5
|
+
sign-in link, a step-up's confirmation code, password changed, e-mail changed, two-factor authentication turned on or
|
|
6
|
+
off, a recovery code used, a sign-in from a new device, and a welcome to a new user**, in English and
|
|
7
7
|
French, with your brand
|
|
8
8
|
in them. `@nxgt/janus` sends no e-mail — its flows answer what to send — and
|
|
9
9
|
this package turns that answer into an e-mail and hands it to any
|
|
@@ -48,7 +48,7 @@ bun add -d typescript
|
|
|
48
48
|
|
|
49
49
|
Three peers, all required: `@nxgt/mail` (0.1 or later, below 2 — 1.0 and
|
|
50
50
|
its transports included), which defines the `Mailer` port and the errors;
|
|
51
|
-
`@nxgt/janus` (0.
|
|
51
|
+
`@nxgt/janus` (0.17), whose flows' answers the methods take — types only,
|
|
52
52
|
nothing of it is loaded; and `typescript` (6). **No Maizzle, no Vue, no
|
|
53
53
|
Tailwind**: they run at this package's build, not in yours.
|
|
54
54
|
|
|
@@ -65,12 +65,12 @@ needs Node `^22.22.3`, `^24.15.0` or `>=26`, for Maizzle — see
|
|
|
65
65
|
|
|
66
66
|
| Export | What it is |
|
|
67
67
|
| --- | --- |
|
|
68
|
-
| `janusMail(options)` | The e-mails, over your mailer: `verifyEmail`, `resetPassword`, `signInCode`, `magicLink`, `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`, `welcome`, and `templates` and `locales`. Options: `mailer`, `from`, `replyTo?`, `brand`, `links` (`verifyEmail`, `resetPassword`, `secureAccount`, `getStarted`, and `recoveryCodes?` and `magicLink?`), `locales?`, `fallbackLocale?`, `templates?`, `clock?`. A wrong option is a bare `TypeError`, thrown here |
|
|
69
|
-
| `janusTemplates()` | The
|
|
68
|
+
| `janusMail(options)` | The e-mails, over your mailer: `verifyEmail`, `resetPassword`, `signInCode`, `magicLink`, `stepUp`, `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`, `newSignIn`, `welcome`, and `templates` and `locales`. Options: `mailer`, `from`, `replyTo?`, `brand`, `links` (`verifyEmail`, `resetPassword`, `secureAccount`, `getStarted`, and `recoveryCodes?` and `magicLink?`), `locales?`, `fallbackLocale?`, `templates?`, `clock?`. A wrong option is a bare `TypeError`, thrown here |
|
|
69
|
+
| `janusTemplates()` | The twelve default templates alone, each `(variables & { locale }) => Rendered` |
|
|
70
70
|
| `JanusMail<L>`, `JanusMailOptions<L>` | What `janusMail()` answers and takes, for the locales `L` |
|
|
71
|
-
| `JanusMailTemplate<V, L>`, `JanusMailTemplates<L>`, `JanusMailTemplateName` | One template, the
|
|
72
|
-
| `JanusMailVariables` | What each template is given: `brand`, and `name`, `link`, `code`, `expiresIn`, `newEmail`, `when` or `
|
|
73
|
-
| `JanusMailSendOptions` | The third argument of `verifyEmail`, `resetPassword`, `signInCode` and `
|
|
71
|
+
| `JanusMailTemplate<V, L>`, `JanusMailTemplates<L>`, `JanusMailTemplateName` | One template, the twelve of them, and their names |
|
|
72
|
+
| `JanusMailVariables` | What each template is given: `brand`, and `name`, `link`, `code`, `expiresIn`, `newEmail`, `when`, `recoveryCodesLeft`, or `device`, `time` and `location`, as its e-mail needs |
|
|
73
|
+
| `JanusMailSendOptions` | The third argument of `verifyEmail`, `resetPassword`, `signInCode`, `magicLink` and `stepUp`: `{ expiresIn? }`, the expiry as text, over the one derived |
|
|
74
74
|
| `JanusMailLocale` | `'en' \| 'fr'`: the locales the defaults are built in |
|
|
75
75
|
| `JanusMailLinks`, `Recipient` | The `links` option; who an e-mail is for — `{ name, locale? }` |
|
|
76
76
|
|
|
@@ -88,14 +88,16 @@ class. To retry a `MailFailure` or trace each send, wrap the mailer you pass:
|
|
|
88
88
|
| `resetPassword(issued, to, options?)` | `issued` from `auth.resetPassword.request(email)`, checked for `null`; a link from `links.resetPassword(issued.token)`, and how long it lasts | `issued.email` |
|
|
89
89
|
| `signInCode(issued, to?, options?)` | `issued.code` from `auth.signInCode.request(email)`, and how long it lasts — **never the challenge** | `issued.email` |
|
|
90
90
|
| `magicLink(issued, to?, options?)` | `issued` from `auth.magicLink.request(email)`, checked for `null`; a link from `links.magicLink(issued.token)`, and how long it lasts. A `TypeError` without `links.magicLink` | `issued.email` |
|
|
91
|
+
| `stepUp(issued, to, options?)` | `issued.code` from `auth.stepUp.request(user)`, narrowed to `via: 'email'`, and how long it lasts, greeting `to.name`, with `links.secureAccount()` — **never the challenge**. A `TypeError` for `via: 'secondFactor'` | `issued.email` |
|
|
91
92
|
| `passwordChanged(to)` | A notice, with `links.secureAccount()` | `to.email` |
|
|
92
93
|
| `emailChanged(to)` | A notice naming `to.newEmail`, with `links.secureAccount()` | `to.formerEmail` |
|
|
93
94
|
| `twoFactorEnabled(to)` | A notice: two-factor authentication was turned on, with `links.secureAccount()` | `to.email` |
|
|
94
95
|
| `twoFactorDisabled(to)` | A notice: two-factor authentication was turned off, with `links.secureAccount()` | `to.email` |
|
|
95
96
|
| `recoveryCodeUsed(to, { when, recoveryCodesLeft })` | A notice: a recovery code was used, `when`, and how many are left — "You have 9 recovery codes left." — with `links.recoveryCodes()`, else `links.secureAccount()` | `to.email` |
|
|
97
|
+
| `newSignIn(to, { device, time, location? })` | A notice: the account was signed in from a new device — which device, `time`, and `location`, `—` when left out — with `links.secureAccount()` | `to.email` |
|
|
96
98
|
| `welcome(to)` | A welcome to a new user — "Welcome, Ada" — with `links.getStarted()` | `to.email` |
|
|
97
99
|
|
|
98
|
-
The
|
|
100
|
+
The five e-mails of a link or a code say how long it lasts — "This link
|
|
99
101
|
expires in 1 hour.", "Ce lien expire dans 1 heure." — from the flow's
|
|
100
102
|
`issued.expiresAt`: the time left at send time, rounded to the minute, then
|
|
101
103
|
down to the largest whole unit (days, hours or minutes), formatted with
|
|
@@ -156,6 +158,23 @@ scanners open every link in an e-mail, and one that signs in on `GET` signs
|
|
|
156
158
|
in for the scanner. [`@nxgt/janus`'s sign-in link guide](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/magic-link.md)
|
|
157
159
|
has that page.
|
|
158
160
|
|
|
161
|
+
### Confirming a sensitive action — the step-up
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const issued = await auth.stepUp.request(current.user); // the user of the session
|
|
165
|
+
if (issued.via === 'email') {
|
|
166
|
+
await mail.stepUp(issued, { name: current.user.name, locale: current.user.locale }); // "Your confirmation code"
|
|
167
|
+
}
|
|
168
|
+
return Response.json({ challenge: issued.challenge, via: issued.via }); // the challenge stays with the user
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
A user whose second factor is active confirms with their app
|
|
172
|
+
(`via: 'secondFactor'`): there is nothing to send. The e-mail names no
|
|
173
|
+
action, and its **Secure my account** button links to
|
|
174
|
+
`links.secureAccount()`, for a user who asked for nothing.
|
|
175
|
+
[`@nxgt/janus`'s step-up guide](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/step-up.md)
|
|
176
|
+
has the confirming route.
|
|
177
|
+
|
|
159
178
|
### Saying the expiry yourself
|
|
160
179
|
|
|
161
180
|
```ts
|
|
@@ -273,6 +292,45 @@ récupération." — or the sentence itself, as text. The button links to
|
|
|
273
292
|
`links.recoveryCodes()`, where the user regenerates their codes, and to
|
|
274
293
|
`links.secureAccount()` when you give no `recoveryCodes`.
|
|
275
294
|
|
|
295
|
+
### Telling a user a new device signed in
|
|
296
|
+
|
|
297
|
+
`@nxgt/janus` 0.17, given `janus({ devices })` and a sign-in's `{ device }`,
|
|
298
|
+
answers `newDevice: true` for a device the user had not signed in from, and
|
|
299
|
+
sends `user.newDeviceSignedIn`. Send the notice from the sign-in's answer,
|
|
300
|
+
where the request is in hand to name the device:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const signedIn = await auth.signIn({ email, password }, { device: cookie ?? null });
|
|
304
|
+
if (signedIn.newDevice) {
|
|
305
|
+
const { name, email, locale } = signedIn.user;
|
|
306
|
+
const time = new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeStyle: 'short', timeZone: 'Europe/Paris' })
|
|
307
|
+
.format(signedIn.session.createdAt);
|
|
308
|
+
await mail.newSignIn({ name, email, locale }, { device: 'Firefox on macOS', time }); // "New sign-in to your account"
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
or from the event, which names the user by id alone:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const auth = janus({
|
|
316
|
+
...config,
|
|
317
|
+
async events(event) {
|
|
318
|
+
if (event.type === 'user.newDeviceSignedIn') {
|
|
319
|
+
const user = await auth.get(event.userId);
|
|
320
|
+
const time = new Intl.DateTimeFormat(user.locale, { dateStyle: 'long', timeStyle: 'short', timeZone: 'Europe/Paris' })
|
|
321
|
+
.format(event.occurredAt);
|
|
322
|
+
await mail.newSignIn({ name: user.name, email: user.email, locale: user.locale }, { device: 'a new device', time });
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Every value is your text, in the recipient's locale: `device` described
|
|
329
|
+
from the request's `User-Agent`, `time` formatted in their time zone.
|
|
330
|
+
**`@nxgt/janus` sees no IP**, so `location` is yours to work out — from a
|
|
331
|
+
geo-IP of your own — and an e-mail without one shows `—` in its place. The
|
|
332
|
+
button links to `links.secureAccount()`.
|
|
333
|
+
|
|
276
334
|
### Replacing a template
|
|
277
335
|
|
|
278
336
|
Any template can be your own function — React Email, a string, another
|
|
@@ -361,14 +419,27 @@ it.** An application that sends no sign-in link leaves it out; calling
|
|
|
361
419
|
rendered or sent. Unlike `recoveryCodes`, it has no fallback: no other page
|
|
362
420
|
can carry the token.
|
|
363
421
|
|
|
422
|
+
**Leave `location` out when you do not know it.** A geo-IP lookup that found
|
|
423
|
+
nothing answers `null`, which is a compile error here and, in JavaScript,
|
|
424
|
+
`janusMail.newSignIn: location must be a string`: spread it in only when it
|
|
425
|
+
is a string — `...(city === null ? {} : { location: city })` — and the
|
|
426
|
+
e-mail shows `—`.
|
|
427
|
+
|
|
428
|
+
**Check `via` before `stepUp`.** `auth.stepUp.request(user)` answers
|
|
429
|
+
`via: 'secondFactor'` — no code, no address — for a user whose second
|
|
430
|
+
factor is active. Passed un-narrowed it is a compile error; in JavaScript,
|
|
431
|
+
`janusMail.stepUp: via must be 'email' — …`, a `TypeError` before anything
|
|
432
|
+
is sent. Unlike `signInCode`, `stepUp` takes the recipient: it greets the
|
|
433
|
+
user by name.
|
|
434
|
+
|
|
364
435
|
**Templates are own properties.** A class's methods live on its prototype
|
|
365
436
|
and are refused with a `TypeError`; its fields (`signInCode = (variables) =>
|
|
366
437
|
…`) pass.
|
|
367
438
|
|
|
368
439
|
**A locale beyond `en` and `fr` needs every template.** The defaults are
|
|
369
|
-
built in those two only, so `locales: ['en', 'fr', 'de']` without all
|
|
370
|
-
templates — `recoveryCodeUsed` included, since 0.5,
|
|
371
|
-
0.7 — is a compile error, and a `TypeError` in JavaScript.
|
|
440
|
+
built in those two only, so `locales: ['en', 'fr', 'de']` without all twelve
|
|
441
|
+
templates — `recoveryCodeUsed` included, since 0.5, `magicLink`, since
|
|
442
|
+
0.7, `stepUp`, since 0.8, and `newSignIn`, since 0.9 — is a compile error, and a `TypeError` in JavaScript.
|
|
372
443
|
|
|
373
444
|
**The brand is text only.** `brand: 'Acme'` is written in the header, the
|
|
374
445
|
body and the footer, escaped: no logo, no link, no markup. For those, replace
|
|
@@ -425,8 +496,8 @@ The symptoms and fixes are in [troubleshooting](docs/troubleshooting.md).
|
|
|
425
496
|
|
|
426
497
|
## Type safety, counted
|
|
427
498
|
|
|
428
|
-
**
|
|
429
|
-
|
|
499
|
+
**Fifty plausible mistakes, fifty refused at compile time.**
|
|
500
|
+
Nine files hold one `@ts-expect-error` per mistake, beside the calls that
|
|
430
501
|
must keep compiling:
|
|
431
502
|
[`test/types/send-refusals.ts`](test/types/send-refusals.ts) the eight of a
|
|
432
503
|
send, 1 to 8;
|
|
@@ -440,9 +511,14 @@ the two-factor notices and the welcome, 26 to 29;
|
|
|
440
511
|
[`test/types/recovery-refusals.ts`](test/types/recovery-refusals.ts) the
|
|
441
512
|
five of the recovery code notice, 30 to 34;
|
|
442
513
|
[`test/types/change-refusals.ts`](test/types/change-refusals.ts) the two of
|
|
443
|
-
the e-mail change notice sent on its event, 35 and 36;
|
|
514
|
+
the e-mail change notice sent on its event, 35 and 36;
|
|
444
515
|
[`test/types/magic-link-refusals.ts`](test/types/magic-link-refusals.ts) the
|
|
445
|
-
four of the sign-in link, 37 to 40
|
|
516
|
+
four of the sign-in link, 37 to 40;
|
|
517
|
+
[`test/types/step-up-refusals.ts`](test/types/step-up-refusals.ts) the six
|
|
518
|
+
of the step-up's code, 41 to 46, beside the names of the templates, held to
|
|
519
|
+
the twelve; and
|
|
520
|
+
[`test/types/new-sign-in-refusals.ts`](test/types/new-sign-in-refusals.ts)
|
|
521
|
+
the four of the new sign-in notice, 47 to 50:
|
|
446
522
|
|
|
447
523
|
1. A sign-in code given to `verifyEmail`: it has no token.
|
|
448
524
|
2. A one-time token given to `signInCode`: it has no code.
|
|
@@ -484,8 +560,18 @@ four of the sign-in link, 37 to 40:
|
|
|
484
560
|
38. `magicLink.request`'s answer, not checked for `null` first.
|
|
485
561
|
39. The token alone given to `magicLink`, rather than what the flow answered.
|
|
486
562
|
40. `links.magicLink` computed asynchronously.
|
|
487
|
-
|
|
488
|
-
|
|
563
|
+
41. `stepUp.request`'s answer not narrowed to `via: 'email'`: an app's step-up has nothing to send.
|
|
564
|
+
42. A step-up confirmed with the user's app, narrowed to it, given to `stepUp`.
|
|
565
|
+
43. A sign-in code given to `stepUp`: it signs in, it confirms no action.
|
|
566
|
+
44. A one-time token given to `stepUp`: it has no code.
|
|
567
|
+
45. `stepUp` without the recipient: its e-mail greets the user by name.
|
|
568
|
+
46. The step-up's `user` given as the recipient when the user type has no name.
|
|
569
|
+
47. The `user.newDeviceSignedIn` event itself given to `newSignIn`: it has neither a name nor an address.
|
|
570
|
+
48. `newSignIn` without the device and the time.
|
|
571
|
+
49. `time` given as the session's `Date` rather than the text in the recipient's locale and time zone.
|
|
572
|
+
50. A location lookup's `null` passed as `location`, rather than leaving it out.
|
|
573
|
+
|
|
574
|
+
In JavaScript, 19 to 23, 26 to 28, 30 to 33 and 35 to 50 are a `TypeError` at send
|
|
489
575
|
time instead, naming the call or the field, and 25, 29 and 34 one from
|
|
490
576
|
`janusMail()`.
|
|
491
577
|
|
package/dist/generated/mail.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/** The e-mails of the build, each with the variables it takes when it is sent. */
|
|
2
2
|
export interface MailEmails {
|
|
3
|
+
"confirm-action": {
|
|
4
|
+
readonly brand: string | number;
|
|
5
|
+
readonly code: string | number;
|
|
6
|
+
readonly expiresIn: string | number;
|
|
7
|
+
readonly link: string;
|
|
8
|
+
readonly name: string | number;
|
|
9
|
+
};
|
|
3
10
|
"email-changed": {
|
|
4
11
|
readonly brand: string | number;
|
|
5
12
|
readonly link: string;
|
|
@@ -11,6 +18,14 @@ export interface MailEmails {
|
|
|
11
18
|
readonly expiresIn: string | number;
|
|
12
19
|
readonly link: string;
|
|
13
20
|
};
|
|
21
|
+
"new-sign-in": {
|
|
22
|
+
readonly brand: string | number;
|
|
23
|
+
readonly device: string | number;
|
|
24
|
+
readonly link: string;
|
|
25
|
+
readonly location: string | number;
|
|
26
|
+
readonly name: string | number;
|
|
27
|
+
readonly time: string | number;
|
|
28
|
+
};
|
|
14
29
|
"password-changed": {
|
|
15
30
|
readonly brand: string | number;
|
|
16
31
|
readonly link: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../src/generated/mail.ts"],"names":[],"mappings":"AAGA,kFAAkF;AAClF,MAAM,WAAW,UAAU;IAC1B,eAAe,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAChJ,YAAY,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9G,kBAAkB,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC/G,oBAAoB,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC9L,gBAAgB,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAClJ,cAAc,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACzH,qBAAqB,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAClH,oBAAoB,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACjH,cAAc,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAChJ,SAAS,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACtG"}
|
|
1
|
+
{"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../src/generated/mail.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* `@nxgt/janus-mail` — the e-mails of `@nxgt/janus`'s flows.
|
|
3
3
|
*
|
|
4
4
|
* `@nxgt/janus` sends no e-mail: its flows answer what to send — a one-time
|
|
5
|
-
* token, a sign-in code, a sign-in link's token,
|
|
6
|
-
*
|
|
5
|
+
* token, a sign-in code, a sign-in link's token, a step-up's code, the
|
|
6
|
+
* address — and this package turns that answer into an e-mail and hands it
|
|
7
|
+
* to an `@nxgt/mail` transport. Twelve e-mails, in
|
|
7
8
|
* English and French, prebuilt with Maizzle when this package is built, and
|
|
8
9
|
* filled at send time with your brand, the recipient's name and your links.
|
|
9
10
|
* Any of them can be replaced by a function of your own.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACX,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,SAAS,GACT,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -117,6 +117,32 @@ function expiringMails(options) {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// src/new-sign-in-mail.ts
|
|
121
|
+
var METHOD = "newSignIn";
|
|
122
|
+
var NO_LOCATION = "—";
|
|
123
|
+
function locationOf(signIn) {
|
|
124
|
+
const given = typeof signIn === "object" && signIn !== null ? signIn.location : undefined;
|
|
125
|
+
if (given === undefined)
|
|
126
|
+
return NO_LOCATION;
|
|
127
|
+
return field(METHOD, signIn, "location");
|
|
128
|
+
}
|
|
129
|
+
function newSignInMail(options) {
|
|
130
|
+
const { brand, links } = options;
|
|
131
|
+
return {
|
|
132
|
+
async newSignIn(to, signIn) {
|
|
133
|
+
return send(options, METHOD, field(METHOD, to, "email"), {
|
|
134
|
+
brand,
|
|
135
|
+
name: field(METHOD, to, "name"),
|
|
136
|
+
device: field(METHOD, signIn, "device"),
|
|
137
|
+
location: locationOf(signIn),
|
|
138
|
+
time: field(METHOD, signIn, "time"),
|
|
139
|
+
link: link(METHOD, links.secureAccount(), "secureAccount()"),
|
|
140
|
+
locale: localeFor(options, to)
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
120
146
|
// src/notice-mails.ts
|
|
121
147
|
function noticeMails(options) {
|
|
122
148
|
const { brand, links } = options;
|
|
@@ -198,24 +224,36 @@ function defaultRenderer() {
|
|
|
198
224
|
}
|
|
199
225
|
|
|
200
226
|
// src/templates.ts
|
|
227
|
+
var recoveryCodeUsed = ({
|
|
228
|
+
brand,
|
|
229
|
+
name,
|
|
230
|
+
when,
|
|
231
|
+
recoveryCodesLeft,
|
|
232
|
+
link,
|
|
233
|
+
locale
|
|
234
|
+
}) => defaultRenderer().render("recovery-code-used", { brand, name, when, recoveryCodesLeft, link }, { locale });
|
|
235
|
+
var newSignIn = ({
|
|
236
|
+
brand,
|
|
237
|
+
name,
|
|
238
|
+
device,
|
|
239
|
+
location,
|
|
240
|
+
time,
|
|
241
|
+
link,
|
|
242
|
+
locale
|
|
243
|
+
}) => defaultRenderer().render("new-sign-in", { brand, name, device, location, time, link }, { locale });
|
|
201
244
|
function janusTemplates() {
|
|
202
245
|
return Object.freeze({
|
|
203
246
|
verifyEmail: ({ brand, name, link, expiresIn, locale }) => defaultRenderer().render("verify-email", { brand, name, link, expiresIn }, { locale }),
|
|
204
247
|
resetPassword: ({ brand, name, link, expiresIn, locale }) => defaultRenderer().render("reset-password", { brand, name, link, expiresIn }, { locale }),
|
|
205
248
|
signInCode: ({ brand, code, expiresIn, locale }) => defaultRenderer().render("sign-in-code", { brand, code, expiresIn }, { locale }),
|
|
206
249
|
magicLink: ({ brand, link, expiresIn, locale }) => defaultRenderer().render("magic-link", { brand, link, expiresIn }, { locale }),
|
|
250
|
+
stepUp: ({ brand, name, code, expiresIn, link, locale }) => defaultRenderer().render("confirm-action", { brand, name, code, expiresIn, link }, { locale }),
|
|
207
251
|
passwordChanged: ({ brand, name, link, locale }) => defaultRenderer().render("password-changed", { brand, name, link }, { locale }),
|
|
208
252
|
emailChanged: ({ brand, name, link, newEmail, locale }) => defaultRenderer().render("email-changed", { brand, name, link, newEmail }, { locale }),
|
|
209
253
|
twoFactorEnabled: ({ brand, name, link, locale }) => defaultRenderer().render("two-factor-enabled", { brand, name, link }, { locale }),
|
|
210
254
|
twoFactorDisabled: ({ brand, name, link, locale }) => defaultRenderer().render("two-factor-disabled", { brand, name, link }, { locale }),
|
|
211
|
-
recoveryCodeUsed
|
|
212
|
-
|
|
213
|
-
name,
|
|
214
|
-
when,
|
|
215
|
-
recoveryCodesLeft,
|
|
216
|
-
link,
|
|
217
|
-
locale
|
|
218
|
-
}) => defaultRenderer().render("recovery-code-used", { brand, name, when, recoveryCodesLeft, link }, { locale }),
|
|
255
|
+
recoveryCodeUsed,
|
|
256
|
+
newSignIn,
|
|
219
257
|
welcome: ({ brand, name, link, locale }) => defaultRenderer().render("welcome", { brand, name, link }, { locale })
|
|
220
258
|
});
|
|
221
259
|
}
|
|
@@ -226,11 +264,13 @@ var TEMPLATE_NAMES = [
|
|
|
226
264
|
"resetPassword",
|
|
227
265
|
"signInCode",
|
|
228
266
|
"magicLink",
|
|
267
|
+
"stepUp",
|
|
229
268
|
"passwordChanged",
|
|
230
269
|
"emailChanged",
|
|
231
270
|
"twoFactorEnabled",
|
|
232
271
|
"twoFactorDisabled",
|
|
233
272
|
"recoveryCodeUsed",
|
|
273
|
+
"newSignIn",
|
|
234
274
|
"welcome"
|
|
235
275
|
];
|
|
236
276
|
var SYSTEM_CLOCK = Object.freeze({ now: () => new Date });
|
|
@@ -386,18 +426,18 @@ function codesLeftText(locale, count) {
|
|
|
386
426
|
}
|
|
387
427
|
|
|
388
428
|
// src/recovery-mail.ts
|
|
389
|
-
var
|
|
429
|
+
var METHOD2 = "recoveryCodeUsed";
|
|
390
430
|
var isCount = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
391
431
|
function codesLeft(used, locale) {
|
|
392
432
|
const given = typeof used === "object" && used !== null ? used.recoveryCodesLeft : undefined;
|
|
393
433
|
if (typeof given === "string")
|
|
394
434
|
return given;
|
|
395
435
|
if (!isCount(given)) {
|
|
396
|
-
throw new TypeError(`janusMail.${
|
|
436
|
+
throw new TypeError(`janusMail.${METHOD2}: recoveryCodesLeft must be a count — a whole number, 0 or more — or the sentence to show`);
|
|
397
437
|
}
|
|
398
438
|
const text = codesLeftText(locale, given);
|
|
399
439
|
if (text === undefined) {
|
|
400
|
-
throw new TypeError(`janusMail.${
|
|
440
|
+
throw new TypeError(`janusMail.${METHOD2}: recoveryCodesLeft must be the sentence to show in a locale the default e-mails are not built in`);
|
|
401
441
|
}
|
|
402
442
|
return text;
|
|
403
443
|
}
|
|
@@ -406,12 +446,38 @@ function recoveryMail(options) {
|
|
|
406
446
|
return {
|
|
407
447
|
async recoveryCodeUsed(to, used) {
|
|
408
448
|
const locale = localeFor(options, to);
|
|
409
|
-
return send(options,
|
|
449
|
+
return send(options, METHOD2, field(METHOD2, to, "email"), {
|
|
410
450
|
brand,
|
|
411
|
-
name: field(
|
|
412
|
-
when: field(
|
|
451
|
+
name: field(METHOD2, to, "name"),
|
|
452
|
+
when: field(METHOD2, used, "when"),
|
|
413
453
|
recoveryCodesLeft: codesLeft(used, locale),
|
|
414
|
-
link: links.recoveryCodes === undefined ? link(
|
|
454
|
+
link: links.recoveryCodes === undefined ? link(METHOD2, links.secureAccount(), "secureAccount()") : link(METHOD2, links.recoveryCodes(), "recoveryCodes()"),
|
|
455
|
+
locale
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/step-up-mail.ts
|
|
462
|
+
var METHOD3 = "stepUp";
|
|
463
|
+
function checkVia(issued) {
|
|
464
|
+
const via = typeof issued === "object" && issued !== null ? issued.via : undefined;
|
|
465
|
+
if (via !== "email") {
|
|
466
|
+
throw new TypeError(`janusMail.${METHOD3}: via must be 'email' — a step-up confirmed with the user's app sends no e-mail`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function stepUpMail(options) {
|
|
470
|
+
const { brand, links } = options;
|
|
471
|
+
return {
|
|
472
|
+
async stepUp(issued, to, sendOptions) {
|
|
473
|
+
checkVia(issued);
|
|
474
|
+
const locale = localeFor(options, to);
|
|
475
|
+
return send(options, METHOD3, field(METHOD3, issued, "email"), {
|
|
476
|
+
brand,
|
|
477
|
+
name: field(METHOD3, to, "name"),
|
|
478
|
+
code: field(METHOD3, issued, "code"),
|
|
479
|
+
expiresIn: expiresInFor(METHOD3, issued, locale, sendOptions, options.clock),
|
|
480
|
+
link: link(METHOD3, links.secureAccount(), "secureAccount()"),
|
|
415
481
|
locale
|
|
416
482
|
});
|
|
417
483
|
}
|
|
@@ -425,6 +491,8 @@ function janusMail(options) {
|
|
|
425
491
|
...expiringMails(resolved),
|
|
426
492
|
...noticeMails(resolved),
|
|
427
493
|
...recoveryMail(resolved),
|
|
494
|
+
...newSignInMail(resolved),
|
|
495
|
+
...stepUpMail(resolved),
|
|
428
496
|
templates: resolved.templates,
|
|
429
497
|
locales: resolved.locales
|
|
430
498
|
});
|
|
@@ -435,5 +503,5 @@ export {
|
|
|
435
503
|
janusTemplates
|
|
436
504
|
};
|
|
437
505
|
|
|
438
|
-
//# debugId=
|
|
506
|
+
//# debugId=F5414A38AA5F17CC64756E2164756E21
|
|
439
507
|
//# sourceMappingURL=index.js.map
|