@nxgt/janus-mail 0.8.0 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-types.d.ts","sourceRoot":"","sources":["../src/link-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC9B,6HAA6H;IAC7H,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `newSignIn`: the notice of a sign-in from a device the user had not signed
3
+ * in from — which device, when, and where when the sender knows — linking to
4
+ * where the account is secured.
5
+ *
6
+ * Apart from the other notices because of its optional `location`: Janus
7
+ * sees no IP, so the sender may know none, and the e-mail then shows `—`,
8
+ * which reads the same in every locale.
9
+ */
10
+ import type { ResolvedOptions } from './options';
11
+ import type { JanusMail } from './types';
12
+ /** What the e-mail shows where the sender knew no location. */
13
+ export declare const NO_LOCATION = "\u2014";
14
+ /** The new sign-in notice, over the resolved options. */
15
+ export declare function newSignInMail(options: ResolvedOptions): Pick<JanusMail<string>, 'newSignIn'>;
16
+ //# sourceMappingURL=new-sign-in-mail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new-sign-in-mail.d.ts","sourceRoot":"","sources":["../src/new-sign-in-mail.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzC,+DAA+D;AAC/D,eAAO,MAAM,WAAW,WAAM,CAAC;AAY/B,yDAAyD;AACzD,wBAAgB,aAAa,CAC5B,OAAO,EAAE,eAAe,GACtB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAgBtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAY1D,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAiJD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoBhE"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAa1D,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAiJD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoBhE"}
@@ -84,6 +84,19 @@ export interface JanusMailVariables {
84
84
  readonly recoveryCodesLeft: string;
85
85
  readonly link: string;
86
86
  };
87
+ /**
88
+ * `device`, `time` and `location` are the sender's text, already in the
89
+ * recipient's locale; `location` is `—` when the sender knew none. `link`
90
+ * is where the account is secured.
91
+ */
92
+ readonly newSignIn: {
93
+ readonly brand: string;
94
+ readonly name: string;
95
+ readonly device: string;
96
+ readonly location: string;
97
+ readonly time: string;
98
+ readonly link: string;
99
+ };
87
100
  /** `name` is in the subject too: "Welcome, Ada". */
88
101
  readonly welcome: {
89
102
  readonly brand: string;
@@ -91,7 +104,7 @@ export interface JanusMailVariables {
91
104
  readonly link: string;
92
105
  };
93
106
  }
94
- /** The name of one of the eleven templates: `verifyEmail`, `resetPassword`, … */
107
+ /** The name of one of the twelve templates: `verifyEmail`, `resetPassword`, … */
95
108
  export type JanusMailTemplateName = keyof JanusMailVariables;
96
109
  /**
97
110
  * One e-mail: its variables and the locale picked for the recipient, in; its
@@ -102,7 +115,7 @@ export type JanusMailTemplateName = keyof JanusMailVariables;
102
115
  export type JanusMailTemplate<V, L extends string = JanusMailLocale> = (variables: V & {
103
116
  readonly locale: L;
104
117
  }) => Rendered | PromiseLike<Rendered>;
105
- /** The eleven templates, each for the locales `L`. */
118
+ /** The twelve templates, each for the locales `L`. */
106
119
  export type JanusMailTemplates<L extends string = JanusMailLocale> = {
107
120
  readonly [K in JanusMailTemplateName]: JanusMailTemplate<JanusMailVariables[K], L>;
108
121
  };
@@ -1 +1 @@
1
- {"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../src/template-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../src/template-types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import type { JanusMailTemplates } from './types';
2
2
  /**
3
- * The eleven default templates, in the locales they are built in. The
3
+ * The twelve default templates, in the locales they are built in. The
4
4
  * renderer behind them is created on the first render, not here.
5
5
  *
6
6
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CAwEnD"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgClD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CA6DnD"}
package/dist/types.d.ts CHANGED
@@ -6,8 +6,10 @@
6
6
  import type { Clock, IssuedCode, IssuedToken, StepUpByEmail } from '@nxgt/janus';
7
7
  import type { Address, Mailer, SentMail, WantedLocales } from '@nxgt/mail';
8
8
  import type { JanusMailLocale } from './generated/locales';
9
+ import type { JanusMailLinks } from './link-types';
9
10
  import type { JanusMailTemplates } from './template-types';
10
11
  export type { JanusMailLocale } from './generated/locales';
12
+ export type { JanusMailLinks } from './link-types';
11
13
  export type { JanusMailTemplate, JanusMailTemplateName, JanusMailTemplates, JanusMailVariables, } from './template-types';
12
14
  /**
13
15
  * Who an e-mail is for, besides the address: the name it greets, and the
@@ -19,52 +21,6 @@ export interface Recipient {
19
21
  readonly name: string;
20
22
  readonly locale?: WantedLocales;
21
23
  }
22
- /**
23
- * The links of the e-mails, each an absolute `http(s)` URL of your
24
- * application. Each is called at send time and must answer a string
25
- * synchronously: an `async` function or a `URL` object is a compile error,
26
- * and from JavaScript, or through a cast, the send throws a `TypeError`
27
- * naming the call. A
28
- * `mailto:` URL is accepted too — `secureAccount: () => 'mailto:security@acme.example'`
29
- * sends the user to your support desk; any other scheme is refused by the
30
- * renderer with a `MailRefused`. The functions are copied when `janusMail()`
31
- * is called: replacing one afterwards changes nothing.
32
- */
33
- export interface JanusMailLinks {
34
- /** The page that confirms an e-mail, given the one-time token: `(token) => \`https://app.example/verify?token=${token}\`` */
35
- readonly verifyEmail: (token: string) => string;
36
- /** The page that sets a new password, given the one-time token. */
37
- readonly resetPassword: (token: string) => string;
38
- /**
39
- * Where a user who did not make a change secures their account — their
40
- * security settings. `passwordChanged`, `emailChanged`, `twoFactorEnabled`
41
- * and `twoFactorDisabled` link to it, and so does `stepUp`, for a user who
42
- * asked for no code.
43
- */
44
- readonly secureAccount: () => string;
45
- /**
46
- * Where a new user starts — your application's home, or its sign-in page
47
- * for an account someone else created. `welcome` links to it, from its
48
- * "Get started" button.
49
- */
50
- readonly getStarted: () => string;
51
- /**
52
- * Where a user regenerates their recovery codes — `recoveryCodeUsed`
53
- * links to it, from its "Secure my account" button. **Optional**: without
54
- * it, that e-mail links to `secureAccount`, the security settings where
55
- * the codes usually are.
56
- */
57
- readonly recoveryCodes?: () => string;
58
- /**
59
- * The page a sign-in link opens, given its one-time token —
60
- * `magicLink` links to it. **Optional**, for an application that sends no
61
- * sign-in link; without it, `magicLink` throws a `TypeError` before
62
- * anything is rendered. That page spends nothing: its button posts the
63
- * token to the route that calls `auth.magicLink.confirm`, so a mail
64
- * scanner that opens the link does not sign in for the user.
65
- */
66
- readonly magicLink?: (token: string) => string;
67
- }
68
24
  /** The options every locale set shares. */
69
25
  interface JanusMailBaseOptions<L extends string> {
70
26
  /** The transport — `@nxgt/mail-smtp`, `@nxgt/mail-resend`, or `createMemoryMailer()` in tests. */
@@ -200,6 +156,24 @@ export interface JanusMail<L extends string = JanusMailLocale> {
200
156
  readonly when: string;
201
157
  readonly recoveryCodesLeft: number | string;
202
158
  }): Promise<SentMail>;
159
+ /**
160
+ * Tells `to.email` their account was signed in from a device they had
161
+ * not signed in from, `time`, on `device` — and `location`, when you know
162
+ * it — with a link to secure the account: once a sign-in answered
163
+ * `newDevice: true`, or on the `user.newDeviceSignedIn` event.
164
+ *
165
+ * Every value is your text, in the recipient's locale: `device` described
166
+ * from the request (`'Firefox on macOS'`), `time` formatted in their time
167
+ * zone. **Janus sees no IP**: `location` is yours to work out, and an
168
+ * e-mail without one shows `—` in its place.
169
+ */
170
+ newSignIn(to: Recipient & {
171
+ readonly email: string;
172
+ }, signIn: {
173
+ readonly device: string;
174
+ readonly time: string;
175
+ readonly location?: string;
176
+ }): Promise<SentMail>;
203
177
  /** The templates in use: the defaults, with `templates` over them. */
204
178
  readonly templates: JanusMailTemplates<L>;
205
179
  /** The locales sent in. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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 step-up's code, 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 |
8
+ | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the step-up's code, the welcome on `user.created`, the recovery code notice on `user.recoveryCodeUsed`, the change notices on `user.passwordChanged` and `user.emailChanged` and the new sign-in notice 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 |
@@ -23,13 +23,13 @@ They mean the same here. These pages add:
23
23
 
24
24
  | Word | Means | Not |
25
25
  | --- | --- | --- |
26
- | **e-mail** | One of the eleven this package sends: `verify-email`, `reset-password`, `sign-in-code`, `magic-link`, `confirm-action`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled`, `recovery-code-used`, `welcome` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
26
+ | **e-mail** | One of the twelve this package sends: `verify-email`, `reset-password`, `sign-in-code`, `magic-link`, `confirm-action`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled`, `recovery-code-used`, `new-sign-in`, `welcome` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
27
27
  | **template** | The function that renders one e-mail: `(variables & { locale }) => Rendered`. Named in camelCase, after the method that sends it: `verifyEmail`, `signInCode` | "renderer" — the renderer is `@nxgt/mail`'s, and reads the build |
28
28
  | **default template** | A template of `janusTemplates()`: the prebuilt e-mail, filled by the renderer | "preset" — a preset is `@nxgt/mail-presets`' source, which the build compiles |
29
29
  | **override** | A template of yours, passed in `templates`, used instead of the default | "custom template" |
30
30
  | **issued** | What a flow answered: an `IssuedToken` from `verifyEmail.send`, `resetPassword.request` or `magicLink.request`, an `IssuedCode` from `signInCode.request`, a `StepUpByEmail` from `stepUp.request` once narrowed to `via: 'email'` | "result", "payload" |
31
31
  | **recipient** | Who an e-mail is for besides the address: a `Recipient`, `{ name, locale? }` | "user" — a user is a record of `@nxgt/janus`; "to" is the address |
32
- | **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`. It links to `links.secureAccount()` — `recoveryCodeUsed` to `links.recoveryCodes()` when given | "alert", "notification" |
32
+ | **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`, `newSignIn`. It links to `links.secureAccount()` — `recoveryCodeUsed` to `links.recoveryCodes()` when given | "alert", "notification" |
33
33
  | **welcome** | The e-mail sent once a user was created, on `@nxgt/janus`'s `user.created`: `welcome`. It links to `links.getStarted()` — not a notice, since it warns of nothing | "onboarding", "greeting" |
34
34
  | **codes left** | How many recovery codes the user still holds, as the recovery code notice says it: `recoveryCodesLeft`, a sentence written from the count with the preset's plural in the recipient's locale — "You have 1 recovery code left." — or passed as text | "remaining codes" — the count itself is `@nxgt/janus`'s `recoveryCodesLeft` |
35
35
  | **expiry** | How long a link or a code lasts, as the e-mail says it: `expiresIn`, derived from the flow's `expiresAt` in the recipient's locale — "1 hour", "1 heure" — or passed per send | "TTL", "lifetime" — the `tokens` TTL is `@nxgt/janus`'s setting, which sets `expiresAt` |
@@ -55,7 +55,7 @@ packages/janus-mail/
55
55
  `src/generated/mail.ts` (`rendererTypes`), from the build.
56
56
  3. `scripts/build-mail.ts` reads the manifest and **fails unless its
57
57
  format is one every `@nxgt/mail` the peer admits reads** — see below —
58
- and **unless exactly the eleven e-mails were built**: a twelfth from a new
58
+ and **unless exactly the twelve e-mails were built**: a thirteenth from a new
59
59
  `@nxgt/mail-presets`, or one missing, stops the build.
60
60
  4. It writes `src/generated/locales.ts` from the manifest's locales, and
61
61
  `src/generated/codes-left.ts` — below — each only when its content
@@ -89,6 +89,8 @@ const templates: JanusMailTemplates<Locale> = {
89
89
  twoFactorDisabled: ({ name, link, locale }) => render('two-factor-disabled', locale, { name, link }),
90
90
  recoveryCodeUsed: ({ name, when, recoveryCodesLeft, link, locale }) =>
91
91
  render('recovery-code-used', locale, { name, when, recoveryCodesLeft, link }),
92
+ newSignIn: ({ name, device, location, time, link, locale }) =>
93
+ render('new-sign-in', locale, { name, device, location, time, link }),
92
94
  welcome: ({ name, link, locale }) => render('welcome', locale, { name, link }),
93
95
  };
94
96
 
@@ -103,7 +105,8 @@ plural of the codes left in `en` and `fr` only, so a German recipient's
103
105
  `mail.recoveryCodeUsed` takes the sentence as text — "Sie haben noch 3
104
106
  Wiederherstellungscodes." — and a count is a `TypeError` there.
105
107
  `recoveryCodeUsed` is the ninth template, since 0.5.0, `magicLink` the
106
- tenth, since 0.7.0, and `stepUp` the eleventh, since 0.8.0: a `templates`
108
+ tenth, since 0.7.0, `stepUp` the eleventh, since 0.8.0, and `newSignIn` the
109
+ twelfth, since 0.9.0: a `templates`
107
110
  written for an earlier version no longer
108
111
  compiles with a locale beyond `en` and `fr` — add the one it lacks. A
109
112
  renderer over a Maizzle build of your own, with
@@ -112,13 +115,13 @@ With only some templates, the call does not compile — the error lands on
112
115
  `templates`:
113
116
 
114
117
  ```text
115
- TS2740: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': welcome, resetPassword, signInCode, magicLink, and 6 more.
118
+ TS2740: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': welcome, resetPassword, signInCode, magicLink, and 7 more.
116
119
  ```
117
120
 
118
121
  and in JavaScript it throws when `janusMail()` is called:
119
122
 
120
123
  ```text
121
- TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, magicLink, stepUp, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, recoveryCodeUsed, welcome missing
124
+ TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, magicLink, stepUp, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, recoveryCodeUsed, newSignIn, welcome missing
122
125
  ```
123
126
 
124
127
  A default template is typed for `'en' | 'fr'`, so reusing one in a wider set
@@ -131,4 +134,4 @@ Built-in locales for more languages are on the [roadmap](../roadmap.md).
131
134
 
132
135
  - `@nxgt/mail`'s [locales guide](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail/docs/guide/locales.md)
133
136
  — `pickLocale` and `parseAcceptLanguage` in full.
134
- - [Templates](templates.md) — writing the eleven for a new locale.
137
+ - [Templates](templates.md) — writing the twelve for a new locale.
@@ -46,7 +46,7 @@ time one is sent, and kept.
46
46
  | `brand` | `string` | required | The name the e-mails show, in the header, the body and the footer. Text: escaped in the HTML |
47
47
  | `links.verifyEmail` | `(token) => string` | required | The page that confirms an address, given the one-time token |
48
48
  | `links.resetPassword` | `(token) => string` | required | The page that sets a new password, given the one-time token |
49
- | `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it, and so does the step-up's code, for a user who asked for none |
49
+ | `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it — the new sign-in's included — and so does the step-up's code, for a user who asked for none |
50
50
  | `links.getStarted` | `() => string` | required | Where a new user starts — your home page, or your sign-in page for an account someone else created: the welcome's **Get started** button links to it |
51
51
  | `links.magicLink` | `(token) => string` | none: `magicLink` is a `TypeError` without it | The page a sign-in link opens, given the one-time token. That page spends nothing: its button posts the token to the route that calls `auth.magicLink.confirm` |
52
52
  | `links.recoveryCodes` | `() => string` | `links.secureAccount` | Where a user regenerates their recovery codes: the recovery code notice links to it, from its **Secure my account** button |
@@ -349,6 +349,52 @@ when opening the session then fails.
349
349
  codes; without it, `links.secureAccount()`. `recoveryCodes` is optional,
350
350
  so a `links` written for 0.4 still compiles.
351
351
 
352
+ ### `newSignIn(to, { device, time, location? })`
353
+
354
+ ```ts
355
+ // From the sign-in's answer, with @nxgt/janus given devices: { keys } and the device cookie:
356
+ const signedIn = await auth.signIn({ email, password }, { device: cookie ?? null });
357
+ if (signedIn.newDevice) {
358
+ const { name, email, locale } = signedIn.user;
359
+ await mail.newSignIn(
360
+ { name, email, locale },
361
+ {
362
+ device: describeDevice(request.headers.get('user-agent')), // yours: 'Firefox on macOS'
363
+ time: new Intl.DateTimeFormat(locale ?? 'en', {
364
+ dateStyle: 'long',
365
+ timeStyle: 'short',
366
+ timeZone: 'Europe/Paris', // the user's, when you keep it
367
+ }).format(signedIn.session.createdAt),
368
+ location: 'Lyon, France', // optional: from a geo-IP of yours
369
+ },
370
+ );
371
+ }
372
+ ```
373
+
374
+ A notice for the account at `to.email`: it was signed in from a device the
375
+ user had not signed in from — which device, where and when — with a
376
+ **Secure my account** button to `links.secureAccount()`. Send it when a
377
+ `@nxgt/janus` 0.17 sign-in answers `newDevice: true`, or on the
378
+ `user.newDeviceSignedIn` event it sends then — the event names the user by
379
+ id alone, so read the name, the locale and the address from the user, and
380
+ it carries no request to describe the device from.
381
+
382
+ - **Every value is your text**, already in the recipient's locale: `device`
383
+ as you describe the request — "Firefox on macOS", "Firefox sur macOS" —
384
+ and `time` formatted in their time zone: "29 septembre 2026 à 09:12".
385
+ The session's `createdAt`, or the event's `occurredAt`, is when it
386
+ happened.
387
+ - **`location` is optional, and yours.** `@nxgt/janus` sees no IP: a
388
+ location comes from a geo-IP of your own, if you have one. Left out, the
389
+ e-mail shows `—` — an em dash, which reads the same in English and
390
+ French — since every line of the summary is always there. `null` is a
391
+ compile error, and in JavaScript `janusMail.newSignIn: location must be a
392
+ string`: spread it in only when you have one,
393
+ `...(city === null ? {} : { location: city })`.
394
+ - **The text part** gives each line of the summary as its label, then its
395
+ value on the next line — `Device`, then `Firefox on macOS` — where every
396
+ other paragraph is one line.
397
+
352
398
  ## The expiry
353
399
 
354
400
  `verifyEmail`, `resetPassword`, `signInCode`, `magicLink` and `stepUp` say
@@ -406,6 +452,7 @@ a `TypeError`, and nothing reaches the mailer: revive the date with
406
452
  | `emailChanged` | `to.formerEmail` | `to.newEmail` |
407
453
  | `twoFactorEnabled`, `twoFactorDisabled` | `to.email` | — |
408
454
  | `recoveryCodeUsed` | `to.email` | — |
455
+ | `newSignIn` | `to.email` | — |
409
456
  | `welcome` | `to.email` | — |
410
457
 
411
458
  `issued.email` is the address the user's record holds, as they registered
@@ -1,7 +1,7 @@
1
1
  # Templates
2
2
 
3
3
  This page is for changing what an e-mail says or looks like: what a template
4
- is given and answers, replacing some of the eleven, using the defaults on their
4
+ is given and answers, replacing some of the twelve, using the defaults on their
5
5
  own, and what the defaults say.
6
6
 
7
7
  ```ts
@@ -22,7 +22,7 @@ const mail = janusMail({
22
22
  });
23
23
  ```
24
24
 
25
- The sign-in code e-mail is now yours; the ten others stay the defaults.
25
+ The sign-in code e-mail is now yours; the eleven others stay the defaults.
26
26
 
27
27
  ## What a template is
28
28
 
@@ -69,14 +69,16 @@ e-mail.
69
69
  | `twoFactorEnabled` | `brand`, `name`, `link` | `mail.twoFactorEnabled(to)` |
70
70
  | `twoFactorDisabled` | `brand`, `name`, `link` | `mail.twoFactorDisabled(to)` |
71
71
  | `recoveryCodeUsed` | `brand`, `name`, `when`, `recoveryCodesLeft`, `link` | `mail.recoveryCodeUsed(to, { when, recoveryCodesLeft })` |
72
+ | `newSignIn` | `brand`, `name`, `device`, `location`, `time`, `link` | `mail.newSignIn(to, { device, time, location? })` |
72
73
  | `welcome` | `brand`, `name`, `link` | `mail.welcome(to)` |
73
74
 
74
75
  Every one also gets `locale`, one of `locales`. Every value is a string:
75
76
  `brand` from the options, `name` from the recipient, `link` from `links` —
76
- `links.secureAccount()` for the four notices and `stepUp`, `links.recoveryCodes()` (else
77
+ `links.secureAccount()` for the notices other than `recoveryCodeUsed` and
78
+ for `stepUp`, `links.recoveryCodes()` (else
77
79
  `links.secureAccount()`) for `recoveryCodeUsed`, `links.getStarted()` for
78
- `welcome` — `code`, `newEmail` and `when` from the
79
- call, and `expiresIn` derived from the flow's `expiresAt` in the locale, or
80
+ `welcome` — `code`, `newEmail`, `when`, `device` and `time` from the
81
+ call, `location` from the call or `—` without one, and `expiresIn` derived from the flow's `expiresAt` in the locale, or
80
82
  the send's own `expiresIn` ([Sending](sending.md#the-expiry)).
81
83
 
82
84
  `recoveryCodesLeft` is a **sentence**, not the count: "You have 9 recovery
@@ -126,11 +128,12 @@ methods live on its prototype and are refused — see
126
128
  [troubleshooting](../troubleshooting.md#janusmail-templatesname-is-not-an-own-enumerable-property--pass-a-plain-object-as--name-variables--rendered-).
127
129
 
128
130
  `templates` is `Partial` while `locales` stays within `en` and `fr`: give
129
- any of the eleven, and the defaults render the rest. Once `locales` holds
130
- another locale, it takes **all eleven** — `stepUp` included, since 0.8 — see
131
+ any of the twelve, and the defaults render the rest. Once `locales` holds
132
+ another locale, it takes **all twelve** — `stepUp` included, since 0.8, and
133
+ `newSignIn`, since 0.9 — see
131
134
  [Adding a locale](locales.md#adding-a-locale).
132
135
 
133
- `mail.templates` answers the eleven in use — yours, and the defaults for the
136
+ `mail.templates` answers the twelve in use — yours, and the defaults for the
134
137
  rest — frozen:
135
138
 
136
139
  ```ts
@@ -152,7 +155,7 @@ const { subject, html, text } = await defaults.resetPassword({
152
155
  });
153
156
  ```
154
157
 
155
- It answers the eleven defaults, typed for `'en' | 'fr'`, without a mailer:
158
+ It answers the twelve defaults, typed for `'en' | 'fr'`, without a mailer:
156
159
  to preview an e-mail, to wrap one — add a line to the default text, say — or
157
160
  to send one through something other than `janusMail()`. A default reused for
158
161
  a wider set of locales is a compile error: it could not render the one it
@@ -192,18 +195,24 @@ The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail
192
195
  | `twoFactorEnabled` | `two-factor-enabled` | Two-factor authentication was turned on | L'authentification à deux facteurs a été activée |
193
196
  | `twoFactorDisabled` | `two-factor-disabled` | Two-factor authentication was turned off | L'authentification à deux facteurs a été désactivée |
194
197
  | `recoveryCodeUsed` | `recovery-code-used` | A recovery code was used on your account | Un code de récupération a été utilisé sur votre compte |
198
+ | `newSignIn` | `new-sign-in` | New sign-in to your account | Nouvelle connexion à votre compte |
195
199
  | `welcome` | `welcome` | Welcome, `Ada` | Bienvenue, `Ada` |
196
200
 
197
201
  Each has a header and a footer with the brand, a heading, a greeting by name
198
202
  (but `signInCode` and `magicLink`), a button with its link and the link again in text for a
199
- client that shows no button, and a text part. The four notices but `recoveryCodeUsed` add a warning:
203
+ client that shows no button, and a text part. The notices other than `recoveryCodeUsed` add a warning:
200
204
  *if this was not you, secure your account now*. `stepUp` shows its code as
201
205
  the sign-in code does, names no action — *someone, we hope you, asked to do
202
206
  something sensitive on your account* — and warns not to share the code if
203
207
  it was not the user, its button saying **Secure my account**. `recoveryCodeUsed` says it
204
208
  too, after a warning banner — *a recovery code was used on your account at
205
209
  `when`* — the codes left, and a line on generating new ones; its button says
206
- **Secure my account**. `welcome` has the brand in
210
+ **Secure my account**. `newSignIn` says the account *was signed in from a
211
+ device we had not seen*, then a summary of three lines — **Device**,
212
+ **Location** and **Time**, *Appareil*, *Lieu* and *Heure* in French — then
213
+ *if this was you, there is nothing to do; if this was not you, secure your
214
+ account now*, its button saying **Secure my account**; in the text part each
215
+ line of the summary is its label, then its value on the next line. `welcome` has the brand in
207
216
  its heading — "Welcome to Acme" — and the recipient's name in its subject,
208
217
  filled at send time like the body; its button says **Get started**.
209
218
 
package/docs/roadmap.md CHANGED
@@ -9,9 +9,8 @@ Nothing between releases.
9
9
 
10
10
  ## Next
11
11
 
12
- - **The other presets** — `new-sign-in` and `invitation` from
13
- `@nxgt/mail-presets`, each once `@nxgt/janus` has a flow that sends it: a
14
- sign-in from a new device, an invitation to a user type.
12
+ - **The invitation** — `invitation` from `@nxgt/mail-presets`, once
13
+ `@nxgt/janus` has a flow that sends it: an invitation to a user type.
15
14
  - **The other presets of `@nxgt/mail-presets` 0.4.0** — `account-deleted`,
16
15
  after a user is deleted, once `@nxgt/janus` has a deletion a link can
17
16
  undo; and `invitation-accepted`, once it has invitations.
@@ -47,6 +46,16 @@ Nothing between releases.
47
46
 
48
47
  Newest first; from the first release on, the package's CHANGELOG holds every one.
49
48
 
49
+ - **The new sign-in notice, v0.9.0.** `newSignIn(to, { device, time,
50
+ location? })`, built from `@nxgt/mail-presets` 1.1.0's `new-sign-in` in
51
+ English and French — "New sign-in to your account", which device, where
52
+ and when, and a **Secure my account** button to `links.secureAccount()`:
53
+ send it when an `@nxgt/janus` 0.17 sign-in answers `newDevice: true`, or
54
+ on `user.newDeviceSignedIn`. Every value is your text in the recipient's
55
+ locale; `@nxgt/janus` sees no IP, so `location` is yours, and `—` without
56
+ one. Twelve e-mails now, and twelve templates to pass for a locale beyond
57
+ `en` and `fr`.
58
+
50
59
  - **The step-up's code, v0.8.0.** `stepUp(issued, to, options?)`, built
51
60
  from `@nxgt/mail-presets` 1.1.0's `confirm-action` in English and French:
52
61
  send what `@nxgt/janus`'s `auth.stepUp.request(user)` answered, once
@@ -121,23 +130,3 @@ Newest first; from the first release on, the package's CHANGELOG holds every one
121
130
  linking to `links.secureAccount()`: send them on `@nxgt/janus` 0.9's
122
131
  `user.secondFactorEnabled` and `user.secondFactorDisabled` events. Seven
123
132
  e-mails now, and seven templates to pass for a locale beyond `en` and `fr`.
124
- - **Dark mode, v0.3.0.** The e-mails follow the reader's dark mode in
125
- every client that supports it, Gmail excepted: a dark page, a dark card and
126
- light text. Built from `@nxgt/mail-ui` 0.4.0, `@nxgt/mail-presets` 0.4.0
127
- and `@nxgt/mail-i18n` 0.5.0; the text parts, subjects and variables are
128
- unchanged, the manifest is still format 1, and the `@nxgt/mail` peer stays
129
- `>=0.1.0 <1`.
130
-
131
- - **The expiry in the e-mail, v0.2.0.** The verification, reset and sign-in
132
- code e-mails say how long the link or code lasts — "1 hour", "1 heure" —
133
- derived from the flow's `expiresAt` in the recipient's locale, measured
134
- against `janusMail({ clock })` (the clock given to `janus()`), or given per
135
- send as `{ expiresIn }`. Two breaks: a template called directly takes
136
- `expiresIn`, and `signInCode` reads `issued.expiresAt`. Built from
137
- `@nxgt/mail-presets` 0.2.0, whose text parts keep each paragraph on one
138
- line.
139
-
140
- - **The first release, v0.1.0.** The five e-mails of `@nxgt/janus`'s flows,
141
- in English and French, over any `@nxgt/mail` transport: `janusMail()` and
142
- `janusTemplates()`. Built with Maizzle when the package is built, shipped
143
- in `mails/`, and only filled in at send time, every value escaped.