@nxgt/janus-telemetry 0.11.1 → 0.13.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 CHANGED
@@ -40,12 +40,16 @@ Every peer is required: `@nxgt/janus` 0.17 (the exact range is in
40
40
  `instanceof JanusError` holds, and one of `@nxgt/telemetry`, so there is one
41
41
  current span. Like `@nxgt/janus`, it expects `"moduleResolution": "bundler"`.
42
42
 
43
+ The `@nxgt/telemetry` floor is tested, not claimed: the package's specs and
44
+ its typecheck run on `@nxgt/telemetry` 0.2.1 as well, in the Floors job, on
45
+ every CI run.
46
+
43
47
  ## API
44
48
 
45
49
  | Export | What it is |
46
50
  | --- | --- |
47
51
  | `instrumentJanus(auth)` | The same `janus()` instance, frozen, with every flow traced under its call path — `auth.signIn` as `janus.signIn`, `auth.patient.signIn` as `janus.patient.signIn` — and the security events written as logs. `cookie` stays synchronous and untraced |
48
- | `instrumentPermissions(access)` | The same `permissions()` instance, frozen, with `can`, `list`, `grant` and `revoke` traced, and one audit event per tuple written |
52
+ | `instrumentPermissions(access)` | The same `permissions()` instance, frozen, with `can`, `list`, `grant`, `revoke` and `replace` traced, and one audit event per tuple written, granted or revoked — for `replace`, one per tuple it added and one per tuple it removed |
49
53
  | `JanusLike`, `PermissionsLike` | What each accepts: `instrumentJanus(access)` and `instrumentPermissions(auth)` do not compile. `PermissionsLike.model.subjects`, which `permissions()` answers, tells a user from a subject set; an instance without it has every `{ type, id, relation }` recorded as a set |
50
54
 
51
55
  ## What is written
@@ -54,10 +58,11 @@ current span. Like `@nxgt/janus`, it expects `"moduleResolution": "bundler"`.
54
58
 
55
59
  | Span | Attributes |
56
60
  | --- | --- |
57
- | `janus.<flow>`, `janus.<type>.<flow>` — `janus.secondFactor.confirm`, `janus.signInCode.confirm`, `janus.magicLink.confirm`, `janus.signInProven` and `janus.verifyEmail.proveFirst` included, the reads as well — `janus.<type>.findByEmail` among them — and a prepared request's `send()` as `janus.magicLink.prepare.send` | `janus.user.type`; `user.id` once the answer names a user, never the address or login a read was asked for; `janus.session.renewed` for `authenticate`; `janus.signIn.status` for `signIn`, `signInCode.confirm`, `magicLink.confirm` and `signInProven` — `signedIn`, or `secondFactor` when it answered a challenge; for `stepUp.confirm`, the `user.id` of the session it stamped, never the session's id |
61
+ | `janus.<flow>`, `janus.<type>.<flow>` — `janus.secondFactor.confirm`, `janus.signInCode.confirm`, `janus.magicLink.confirm`, `janus.signInProven` and `janus.verifyEmail.proveFirst` included, the reads as well — `janus.<type>.findByEmail` and `janus.<type>.findOrCreate` among them — and a prepared request's `send()` as `janus.magicLink.prepare.send` | `janus.user.type`; `user.id` once the answer names a user, never the address or login a read was asked for; `janus.session.renewed` for `authenticate`; `janus.signIn.status` for `signIn`, `signInCode.confirm`, `magicLink.confirm` and `signInProven` — `signedIn`, or `secondFactor` when it answered a challenge; for `stepUp.confirm`, the `user.id` of the session it stamped, never the session's id |
58
62
  | `janus.can` | `janus.subject.type`, `janus.subject.id`, `janus.permission`, `janus.object.type`, `janus.object.id`, and the answer, `janus.allowed` |
59
63
  | `janus.list` | the subject, `janus.permission`, `janus.object.type`, and `janus.page.items`, how many it found |
60
64
  | `janus.grant`, `janus.revoke` | the object, `janus.relation`, and the subject — `janus.subject.relation` for a subject set, read as `permissions()` reads it: a user with a field named `relation` is that user, and a set on a user type is one only when `setOf()` made it |
65
+ | `janus.replace` | the object and `janus.relation`; the tuples it added and removed are the audit events, below, each with the object, relation and subject a `grant` or `revoke` writes — a subject set always with its `janus.subject.relation`, since a stored tuple names it |
61
66
 
62
67
  **A refusal is an answer, not a failure.** A wrong password, a taken login, a
63
68
  spent token, a denial: the span is `ok`, and a refusal carries
@@ -80,7 +85,7 @@ when the flow knows them — `janus.signOut` carries neither:
80
85
  | `janus.signIn.secondFactor` | info | the password, an e-mailed code, a sign-in link or the application's own proof was right and a second factor's code was asked for: `signIn`, `signInCode.confirm`, `magicLink.confirm` or `signInProven` answered a challenge. `janus.user.type` and the `user.id` the challenge answer carries — never the login typed |
81
86
  | `janus.signIn.refused` | **warn** | a sign-in was refused, with `janus.refusal` — `CREDENTIALS_INVALID` with `janus.refusal.reason` (`unknownLogin`, `noPassword`, `wrongPassword`), `USER_INACTIVE` with the `user.id` of the deactivated user, or a refused `secondFactor.confirm` or `signInCode.confirm`: `CODE_INVALID` with `user.id` and `janus.secondFactor.attemptsLeft` — the same attribute for both — a `TOKEN_*` code (`TOKEN_STALE` with `user.id`), `SECOND_FACTOR_NOT_ENROLLED`, or `VERSION_CONFLICT` for a write that raced. Every refusal of `signInCode.confirm` adds `janus.signIn.code: true`, every refusal of `magicLink.confirm` — a `TOKEN_*` code, `USER_INACTIVE` or `VERSION_CONFLICT` — adds `janus.signIn.magicLink: true`, every refusal of `signInProven` — `USER_INACTIVE` or `NOT_FOUND` — adds `janus.signIn.proven: true`, and every refusal of `secondFactor.recover` adds `janus.signIn.recoveryCode: true` |
82
87
  | `janus.signIn.throttled` | **warn** | `signIn` refused a login past its attempts in the window — `@nxgt/janus`'s throttle, the right password included — with `janus.refusal: CREDENTIALS_INVALID`, `janus.refusal.reason: throttled`, `janus.user.type` and `janus.signIn.retryAfter`, the seconds until the next window. Never the login, never a `user.id`: an unknown login is throttled alike. Written instead of `janus.signIn.refused`, since no password was compared |
83
- | `janus.mail.throttled` | **warn** | `magicLink.request`, `signInCode.request`, `resetPassword.request`, their `prepare`, `verifyEmail.send` or `stepUp.request` was refused past its attempts in the window — `@nxgt/janus`'s mail throttle — with `janus.refusal: MAIL_THROTTLED`, `janus.user.type`, `user.id` (the per-user flows only), `janus.mail.flow` (e.g. `magicLink.request`, or `magicLink.prepare`) and `janus.mail.retryAfter`, the seconds until the window ends (the last link or code sent still works). Never the address: an unknown address is throttled alike. The span stays ok |
88
+ | `janus.mail.throttled` | **warn** | `magicLink.request`, `signInCode.request`, `resetPassword.request`, their `prepare`, `verifyEmail.send`, `changeEmail.send` or `stepUp.request` was refused past its attempts in the window — `@nxgt/janus`'s mail throttle — with `janus.refusal: MAIL_THROTTLED`, `janus.user.type`, `user.id` (the per-user flows only), `janus.mail.flow` (e.g. `magicLink.request`, or `magicLink.prepare`) and `janus.mail.retryAfter`, the seconds until the window ends (the last link or code sent still works). Never the address: an unknown address is throttled alike. The span stays ok |
84
89
  | `janus.secondFactor.enrolled`, `janus.secondFactor.activated`, `janus.secondFactor.disabled` | info | `enroll`, `activate` and `disable`, with the `user.id` they were called for |
85
90
  | `janus.secondFactor.recoveryCodesRegenerated` | info | `regenerateRecoveryCodes`, with the `user.id` it was called for — never a code |
86
91
  | `janus.stepUp.asked` | info | `stepUp.request` issued a challenge, with the `user.id` and `janus.stepUp.via` — `email` when a code is to be sent, `secondFactor` when the user's app confirms it |
@@ -92,7 +97,8 @@ when the flow knows them — `janus.signOut` carries neither:
92
97
  | `janus.user.activated`, `janus.user.deactivated` | info | `setActive` |
93
98
  | `janus.password.changed`, `janus.password.set`, `janus.password.reset` | info | the user's change, an admin's, a reset |
94
99
  | `janus.email.verified` | info | an e-mail was confirmed — by `verifyEmail.confirm`, or proved on the application's own proof by `verifyEmail.proveFirst`, which adds `janus.email.proveFirst: true` and writes it only when it proved (an e-mail already proved writes nothing) |
95
- | `janus.tuple.granted`, `janus.tuple.revoked` | info | a tuple was written, with the object, relation and subject |
100
+ | `janus.email.changed` | info | a user's e-mail moved, with `janus.user.type`, `user.id` and `janus.email.proved` — never the former or the new address. `changeEmail.confirm` writes it with `janus.email.proved: true` (and no `janus.email.verified` besides: the move is the proof); `update` writes it with `janus.email.proved: false` when its patch names the `email` field, even if the address turns out unchanged (the field name janus defaults to — a renamed e-mail field is not seen on `update`, nor an `update` that moved the address and then answered `STORE_FAILED` while spending the change links) |
101
+ | `janus.tuple.granted`, `janus.tuple.revoked` | info | a tuple was written by `grant`, `revoke` or `replace` — one event per tuple, `replace` one per tuple added and one per tuple removed — with the object, relation and subject |
96
102
 
97
103
  ## Traps
98
104
 
@@ -1,8 +1,43 @@
1
1
  import type { Outcome } from '../traced';
2
2
  import type { Call } from './call';
3
+ export declare const log: import("@nxgt/telemetry").Logger;
4
+ /** The events a security review reads: who signed in, out, up, stepped up, and what changed. */
5
+ export declare const events: {
6
+ signedUp: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
7
+ signedIn: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
8
+ signInRefused: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
9
+ signInThrottled: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
10
+ secondFactorAsked: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
11
+ signInCodeSent: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
12
+ magicLinkSent: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
13
+ stepUpAsked: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
14
+ stepUpConfirmed: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
15
+ stepUpRefused: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
16
+ secondFactorEnrolled: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
17
+ secondFactorActivated: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
18
+ secondFactorDisabled: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
19
+ recoveryCodesRegenerated: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
20
+ signedOut: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
21
+ signedOutEverywhere: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
22
+ userDeleted: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
23
+ userActivated: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
24
+ userDeactivated: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
25
+ passwordChanged: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
26
+ passwordSet: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
27
+ passwordReset: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
28
+ emailVerified: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
29
+ emailChanged: (input?: Readonly<Record<string, unknown>>) => import("@nxgt/telemetry").TelemetryEvent;
30
+ };
3
31
  /**
4
- * What each flow writes once it answered. Nothing here reads a login, an
5
- * e-mail, a password or a token: only user types, ids, codes and reasons.
32
+ * What a sign-in other than the password's writes once answered — an
33
+ * e-mailed code or link, or the application's own proof — marked, so an
34
+ * alert on refusals can tell one from another and from a password: a
35
+ * refusal, the second factor asked for, or a sign-in.
6
36
  */
7
- export declare const WRITTEN: Readonly<Record<string, (call: Call, outcome: Outcome) => void>>;
37
+ export declare function markedSignIn(mark: string): (call: Call, outcome: Outcome) => void;
38
+ /**
39
+ * What a sign-in code or link issued writes: `sent`, with its user — only
40
+ * when one was issued, since `null` is nobody and says nothing.
41
+ */
42
+ export declare function issued(sent: typeof events.signInCodeSent): (call: Call, outcome: Outcome) => void;
8
43
  //# sourceMappingURL=events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/flows/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AA0FnC;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAkJtD,CAAC"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/flows/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AASnC,eAAO,MAAM,GAAG,kCAA8B,CAAC;AAE/C,gGAAgG;AAChG,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;CA2BlB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,GACV,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAsBxC;AAED;;;GAGG;AACH,wBAAgB,MAAM,CACrB,IAAI,EAAE,OAAO,MAAM,CAAC,cAAc,GAChC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAMxC"}
@@ -0,0 +1,8 @@
1
+ import type { Outcome } from '../traced';
2
+ import type { Call } from './call';
3
+ /**
4
+ * What each flow writes once it answered. Nothing here reads a login, an
5
+ * e-mail, a password or a token: only user types, ids, codes and reasons.
6
+ */
7
+ export declare const WRITTEN: Readonly<Record<string, (call: Call, outcome: Outcome) => void>>;
8
+ //# sourceMappingURL=written.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"written.d.ts","sourceRoot":"","sources":["../../src/flows/written.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AA6BnC;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CA2KtD,CAAC"}
package/dist/index.js CHANGED
@@ -86,9 +86,6 @@ async function traced(name, fields, call, after) {
86
86
  return ended.value;
87
87
  }
88
88
 
89
- // src/flows/events.ts
90
- import { createLogger as createLogger2, event as event2 } from "@nxgt/telemetry";
91
-
92
89
  // src/flows/fields.ts
93
90
  function userFields(call, value) {
94
91
  const user = typeof value === "object" && value !== null && "user" in value ? value.user : value;
@@ -146,62 +143,46 @@ function provedOf(value) {
146
143
  return typeof value === "object" && value !== null && "proved" in value && value.proved === true;
147
144
  }
148
145
 
149
- // src/flows/mail.ts
146
+ // src/flows/events.ts
150
147
  import { createLogger, event } from "@nxgt/telemetry";
151
148
  var log = createLogger("@nxgt/janus");
152
- var mailThrottled = event("janus.mail.throttled");
153
- function mailRequest(written) {
154
- return (call, outcome) => {
155
- if (!outcome.ok && outcome.refusal.code === "MAIL_THROTTLED") {
156
- log.warn(mailThrottled({
157
- ...refusalFields(call, outcome.refusal),
158
- "janus.mail.flow": call.flow,
159
- ...outcome.refusal.retryAfter === undefined ? {} : { "janus.mail.retryAfter": outcome.refusal.retryAfter }
160
- }));
161
- return;
162
- }
163
- written?.(call, outcome);
164
- };
165
- }
166
-
167
- // src/flows/events.ts
168
- var log2 = createLogger2("@nxgt/janus");
169
149
  var events = {
170
- signedUp: event2("janus.signUp"),
171
- signedIn: event2("janus.signIn"),
172
- signInRefused: event2("janus.signIn.refused"),
173
- signInThrottled: event2("janus.signIn.throttled"),
174
- secondFactorAsked: event2("janus.signIn.secondFactor"),
175
- signInCodeSent: event2("janus.signInCode.sent"),
176
- magicLinkSent: event2("janus.magicLink.sent"),
177
- stepUpAsked: event2("janus.stepUp.asked"),
178
- stepUpConfirmed: event2("janus.stepUp.confirmed"),
179
- stepUpRefused: event2("janus.stepUp.refused"),
180
- secondFactorEnrolled: event2("janus.secondFactor.enrolled"),
181
- secondFactorActivated: event2("janus.secondFactor.activated"),
182
- secondFactorDisabled: event2("janus.secondFactor.disabled"),
183
- recoveryCodesRegenerated: event2("janus.secondFactor.recoveryCodesRegenerated"),
184
- signedOut: event2("janus.signOut"),
185
- signedOutEverywhere: event2("janus.signOutEverywhere"),
186
- userDeleted: event2("janus.user.deleted"),
187
- userActivated: event2("janus.user.activated"),
188
- userDeactivated: event2("janus.user.deactivated"),
189
- passwordChanged: event2("janus.password.changed"),
190
- passwordSet: event2("janus.password.set"),
191
- passwordReset: event2("janus.password.reset"),
192
- emailVerified: event2("janus.email.verified")
150
+ signedUp: event("janus.signUp"),
151
+ signedIn: event("janus.signIn"),
152
+ signInRefused: event("janus.signIn.refused"),
153
+ signInThrottled: event("janus.signIn.throttled"),
154
+ secondFactorAsked: event("janus.signIn.secondFactor"),
155
+ signInCodeSent: event("janus.signInCode.sent"),
156
+ magicLinkSent: event("janus.magicLink.sent"),
157
+ stepUpAsked: event("janus.stepUp.asked"),
158
+ stepUpConfirmed: event("janus.stepUp.confirmed"),
159
+ stepUpRefused: event("janus.stepUp.refused"),
160
+ secondFactorEnrolled: event("janus.secondFactor.enrolled"),
161
+ secondFactorActivated: event("janus.secondFactor.activated"),
162
+ secondFactorDisabled: event("janus.secondFactor.disabled"),
163
+ recoveryCodesRegenerated: event("janus.secondFactor.recoveryCodesRegenerated"),
164
+ signedOut: event("janus.signOut"),
165
+ signedOutEverywhere: event("janus.signOutEverywhere"),
166
+ userDeleted: event("janus.user.deleted"),
167
+ userActivated: event("janus.user.activated"),
168
+ userDeactivated: event("janus.user.deactivated"),
169
+ passwordChanged: event("janus.password.changed"),
170
+ passwordSet: event("janus.password.set"),
171
+ passwordReset: event("janus.password.reset"),
172
+ emailVerified: event("janus.email.verified"),
173
+ emailChanged: event("janus.email.changed")
193
174
  };
194
175
  function markedSignIn(mark) {
195
176
  return (call, outcome) => {
196
177
  if (!outcome.ok) {
197
- log2.warn(events.signInRefused({
178
+ log.warn(events.signInRefused({
198
179
  ...refusalFields(call, outcome.refusal),
199
180
  [mark]: true
200
181
  }));
201
182
  } else if (statusOf(outcome.value) === "secondFactor") {
202
- log2.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
183
+ log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
203
184
  } else {
204
- log2.info(events.signedIn({
185
+ log.info(events.signedIn({
205
186
  ...signedInFields(call, outcome.value),
206
187
  [mark]: true
207
188
  }));
@@ -211,27 +192,52 @@ function markedSignIn(mark) {
211
192
  function issued(sent) {
212
193
  return (call, outcome) => {
213
194
  if (outcome.ok && outcome.value !== null) {
214
- log2.info(sent(userFields(call, outcome.value)));
195
+ log.info(sent(userFields(call, outcome.value)));
215
196
  }
216
197
  };
217
198
  }
199
+
200
+ // src/flows/mail.ts
201
+ import { createLogger as createLogger2, event as event2 } from "@nxgt/telemetry";
202
+ var log2 = createLogger2("@nxgt/janus");
203
+ var mailThrottled = event2("janus.mail.throttled");
204
+ function mailRequest(written) {
205
+ return (call, outcome) => {
206
+ if (!outcome.ok && outcome.refusal.code === "MAIL_THROTTLED") {
207
+ log2.warn(mailThrottled({
208
+ ...refusalFields(call, outcome.refusal),
209
+ "janus.mail.flow": call.flow,
210
+ ...outcome.refusal.retryAfter === undefined ? {} : { "janus.mail.retryAfter": outcome.refusal.retryAfter }
211
+ }));
212
+ return;
213
+ }
214
+ written?.(call, outcome);
215
+ };
216
+ }
217
+
218
+ // src/flows/written.ts
219
+ var EMAIL_FIELD = "email";
220
+ function patchNamesEmail(call) {
221
+ const patch = call.args[1];
222
+ return typeof patch === "object" && patch !== null && patch[EMAIL_FIELD] !== undefined;
223
+ }
218
224
  var WRITTEN = {
219
225
  signUp: (call, outcome) => {
220
226
  if (outcome.ok)
221
- log2.info(events.signedUp(userFields(call, outcome.value)));
227
+ log.info(events.signedUp(userFields(call, outcome.value)));
222
228
  },
223
229
  signIn: (call, outcome) => {
224
230
  if (!outcome.ok && outcome.refusal.reason === "throttled") {
225
- log2.warn(events.signInThrottled({
231
+ log.warn(events.signInThrottled({
226
232
  ...refusalFields(call, outcome.refusal),
227
233
  "janus.signIn.retryAfter": outcome.refusal.retryAfter
228
234
  }));
229
235
  } else if (!outcome.ok) {
230
- log2.warn(events.signInRefused(refusalFields(call, outcome.refusal)));
236
+ log.warn(events.signInRefused(refusalFields(call, outcome.refusal)));
231
237
  } else if (statusOf(outcome.value) === "secondFactor") {
232
- log2.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
238
+ log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
233
239
  } else {
234
- log2.info(events.signedIn(signedInFields(call, outcome.value)));
240
+ log.info(events.signedIn(signedInFields(call, outcome.value)));
235
241
  }
236
242
  },
237
243
  "signInCode.request": mailRequest(issued(events.signInCodeSent)),
@@ -245,7 +251,7 @@ var WRITTEN = {
245
251
  signInProven: markedSignIn("janus.signIn.proven"),
246
252
  "stepUp.request": mailRequest((call, outcome) => {
247
253
  if (outcome.ok) {
248
- log2.info(events.stepUpAsked({
254
+ log.info(events.stepUpAsked({
249
255
  ...userFields(call, outcome.value),
250
256
  "janus.stepUp.via": viaOf(outcome.value)
251
257
  }));
@@ -253,30 +259,30 @@ var WRITTEN = {
253
259
  }),
254
260
  "stepUp.confirm": (call, outcome) => {
255
261
  if (outcome.ok) {
256
- log2.info(events.stepUpConfirmed(sessionUserFields(call, outcome.value)));
262
+ log.info(events.stepUpConfirmed(sessionUserFields(call, outcome.value)));
257
263
  } else {
258
- log2.warn(events.stepUpRefused(refusalFields(call, outcome.refusal)));
264
+ log.warn(events.stepUpRefused(refusalFields(call, outcome.refusal)));
259
265
  }
260
266
  },
261
267
  "secondFactor.confirm": (call, outcome) => {
262
268
  if (outcome.ok) {
263
- log2.info(events.signedIn({
269
+ log.info(events.signedIn({
264
270
  ...signedInFields(call, outcome.value),
265
271
  "janus.signIn.secondFactor": true
266
272
  }));
267
273
  } else {
268
- log2.warn(events.signInRefused(refusalFields(call, outcome.refusal)));
274
+ log.warn(events.signInRefused(refusalFields(call, outcome.refusal)));
269
275
  }
270
276
  },
271
277
  "secondFactor.recover": (call, outcome) => {
272
278
  if (outcome.ok) {
273
- log2.info(events.signedIn({
279
+ log.info(events.signedIn({
274
280
  ...signedInFields(call, outcome.value),
275
281
  "janus.signIn.recoveryCode": true,
276
282
  "janus.secondFactor.recoveryCodesLeft": recoveryCodesLeft(outcome.value)
277
283
  }));
278
284
  } else {
279
- log2.warn(events.signInRefused({
285
+ log.warn(events.signInRefused({
280
286
  ...refusalFields(call, outcome.refusal),
281
287
  "janus.signIn.recoveryCode": true
282
288
  }));
@@ -284,62 +290,79 @@ var WRITTEN = {
284
290
  },
285
291
  "secondFactor.regenerateRecoveryCodes": (call, outcome) => {
286
292
  if (outcome.ok) {
287
- log2.info(events.recoveryCodesRegenerated(argumentUser(call)));
293
+ log.info(events.recoveryCodesRegenerated(argumentUser(call)));
288
294
  }
289
295
  },
290
296
  "secondFactor.enroll": (call, outcome) => {
291
297
  if (outcome.ok)
292
- log2.info(events.secondFactorEnrolled(argumentUser(call)));
298
+ log.info(events.secondFactorEnrolled(argumentUser(call)));
293
299
  },
294
300
  "secondFactor.activate": (call, outcome) => {
295
301
  if (outcome.ok)
296
- log2.info(events.secondFactorActivated(argumentUser(call)));
302
+ log.info(events.secondFactorActivated(argumentUser(call)));
297
303
  },
298
304
  "secondFactor.disable": (call, outcome) => {
299
305
  if (outcome.ok)
300
- log2.info(events.secondFactorDisabled(argumentUser(call)));
306
+ log.info(events.secondFactorDisabled(argumentUser(call)));
301
307
  },
302
308
  signOut: (_call, outcome) => {
303
309
  if (outcome.ok && outcome.value === true)
304
- log2.info(events.signedOut());
310
+ log.info(events.signedOut());
305
311
  },
306
312
  signOutEverywhere: (call, outcome) => {
307
313
  if (outcome.ok)
308
- log2.info(events.signedOutEverywhere(argumentUser(call)));
314
+ log.info(events.signedOutEverywhere(argumentUser(call)));
309
315
  },
310
316
  delete: (call, outcome) => {
311
317
  if (outcome.ok && outcome.value === true) {
312
- log2.info(events.userDeleted(argumentUser(call)));
318
+ log.info(events.userDeleted(argumentUser(call)));
313
319
  }
314
320
  },
315
321
  setActive: (call, outcome) => {
316
322
  if (!outcome.ok)
317
323
  return;
318
324
  const written = call.args[1] === true ? events.userActivated : events.userDeactivated;
319
- log2.info(written(argumentUser(call)));
325
+ log.info(written(argumentUser(call)));
320
326
  },
321
327
  changePassword: (call, outcome) => {
322
328
  if (outcome.ok)
323
- log2.info(events.passwordChanged(argumentUser(call)));
329
+ log.info(events.passwordChanged(argumentUser(call)));
324
330
  },
325
331
  setPassword: (call, outcome) => {
326
332
  if (outcome.ok)
327
- log2.info(events.passwordSet(argumentUser(call)));
333
+ log.info(events.passwordSet(argumentUser(call)));
328
334
  },
329
335
  "resetPassword.request": mailRequest(),
330
336
  "resetPassword.prepare": mailRequest(),
331
337
  "verifyEmail.send": mailRequest(),
338
+ "changeEmail.send": mailRequest(),
332
339
  "resetPassword.confirm": (call, outcome) => {
333
340
  if (outcome.ok)
334
- log2.info(events.passwordReset(userFields(call, outcome.value)));
341
+ log.info(events.passwordReset(userFields(call, outcome.value)));
335
342
  },
336
343
  "verifyEmail.confirm": (call, outcome) => {
337
344
  if (outcome.ok)
338
- log2.info(events.emailVerified(userFields(call, outcome.value)));
345
+ log.info(events.emailVerified(userFields(call, outcome.value)));
346
+ },
347
+ "changeEmail.confirm": (call, outcome) => {
348
+ if (outcome.ok) {
349
+ log.info(events.emailChanged({
350
+ ...userFields(call, outcome.value),
351
+ "janus.email.proved": true
352
+ }));
353
+ }
354
+ },
355
+ update: (call, outcome) => {
356
+ if (outcome.ok && patchNamesEmail(call)) {
357
+ log.info(events.emailChanged({
358
+ ...userFields(call, outcome.value),
359
+ "janus.email.proved": false
360
+ }));
361
+ }
339
362
  },
340
363
  "verifyEmail.proveFirst": (call, outcome) => {
341
364
  if (outcome.ok && provedOf(outcome.value)) {
342
- log2.info(events.emailVerified({
365
+ log.info(events.emailVerified({
343
366
  ...userFields(call, outcome.value),
344
367
  "janus.email.proveFirst": true
345
368
  }));
@@ -449,8 +472,41 @@ var tracersFor = (userTypes) => ({
449
472
  }
450
473
  }),
451
474
  grant: (fn) => tupleWrite("janus.grant", granted, fn, userTypes),
452
- revoke: (fn) => tupleWrite("janus.revoke", revoked, fn, userTypes)
475
+ revoke: (fn) => tupleWrite("janus.revoke", revoked, fn, userTypes),
476
+ replace: (fn) => replaceWrite(fn)
453
477
  });
478
+ function storedFields(tuple) {
479
+ if (typeof tuple !== "object" || tuple === null)
480
+ return {};
481
+ const { object, relation, subject } = tuple;
482
+ const setRelation = typeof subject === "object" && subject !== null && "relation" in subject ? subject.relation : undefined;
483
+ return fieldsOf({
484
+ ...objectFields(object),
485
+ "janus.relation": typeof relation === "string" ? relation : undefined,
486
+ "janus.subject.type": typeOf(subject),
487
+ "janus.subject.id": idOf(subject),
488
+ "janus.subject.relation": typeof setRelation === "string" ? setRelation : undefined
489
+ });
490
+ }
491
+ function replaceWrite(fn) {
492
+ return (...args) => traced("janus.replace", fieldsOf({
493
+ ...objectFields(args[0]),
494
+ "janus.relation": typeof args[1] === "string" ? args[1] : undefined
495
+ }), () => fn(...args), (_scope, outcome) => {
496
+ if (!outcome.ok || typeof outcome.value !== "object")
497
+ return;
498
+ const { added, removed } = outcome.value ?? {};
499
+ for (const [tuples, written] of [
500
+ [added, granted],
501
+ [removed, revoked]
502
+ ]) {
503
+ if (!Array.isArray(tuples))
504
+ continue;
505
+ for (const tuple of tuples)
506
+ log3.info(written(storedFields(tuple)));
507
+ }
508
+ });
509
+ }
454
510
  function tupleWrite(name, written, fn, userTypes) {
455
511
  return (...args) => {
456
512
  const fields = fieldsOf({
@@ -480,5 +536,5 @@ export {
480
536
  instrumentPermissions
481
537
  };
482
538
 
483
- //# debugId=F208819435F0AAFC64756E2164756E21
539
+ //# debugId=D06ADE7604EC6B3A64756E2164756E21
484
540
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/traced.ts", "../src/flows/events.ts", "../src/flows/fields.ts", "../src/flows/mail.ts", "../src/flows/trace.ts", "../src/flows/index.ts", "../src/permissions.ts"],
3
+ "sources": ["../src/traced.ts", "../src/flows/fields.ts", "../src/flows/events.ts", "../src/flows/mail.ts", "../src/flows/written.ts", "../src/flows/trace.ts", "../src/flows/index.ts", "../src/permissions.ts"],
4
4
  "sourcesContent": [
5
5
  "import { JanusError, type JanusErrorCode } from '@nxgt/janus';\nimport { type SpanScope, span } from '@nxgt/telemetry';\n\n/**\n * What each code is. A **failure** is the server's to fix, and fails its span;\n * a **refusal** — a wrong password, a taken login, a spent token — is an\n * answer, and leaves it `ok`. Exhaustive, so a new code does not compile until\n * it is classified.\n */\nfunction kindOf(code: JanusErrorCode): 'failure' | 'refusal' {\n\tswitch (code) {\n\t\tcase 'STORE_FAILED':\n\t\tcase 'UNSUPPORTED':\n\t\tcase 'PERMISSION_DEPTH':\n\t\tcase 'HASH_UNSUPPORTED':\n\t\t\treturn 'failure';\n\t\tcase 'NOT_FOUND':\n\t\tcase 'LOGIN_TAKEN':\n\t\tcase 'VERSION_CONFLICT':\n\t\tcase 'USER_INVALID':\n\t\tcase 'PASSWORD_TOO_SHORT':\n\t\tcase 'CREDENTIALS_INVALID':\n\t\tcase 'USER_INACTIVE':\n\t\tcase 'STEP_UP_REQUIRED':\n\t\tcase 'TOKEN_UNKNOWN':\n\t\tcase 'TOKEN_SPENT':\n\t\tcase 'TOKEN_EXPIRED':\n\t\tcase 'TOKEN_STALE':\n\t\tcase 'CODE_INVALID':\n\t\tcase 'MAIL_THROTTLED':\n\t\tcase 'SECOND_FACTOR_NOT_ENROLLED':\n\t\tcase 'SECOND_FACTOR_ACTIVE':\n\t\tcase 'INVALID_CURSOR':\n\t\t\treturn 'refusal';\n\t}\n}\n\n/** A `JanusError` the caller caused; anything else, or a failure code, is not. */\nexport function isRefusal(error: unknown): error is JanusError {\n\treturn error instanceof JanusError && kindOf(error.code) === 'refusal';\n}\n\n/** What a span or an event carries: scalars only, and never an absent one. */\nexport type Fields = Readonly<Record<string, string | number | boolean>>;\n\n/** `fields` without its `undefined` values. */\nexport function fieldsOf(\n\tfields: Readonly<Record<string, string | number | boolean | undefined>>,\n): Fields {\n\tconst kept: Record<string, string | number | boolean> = {};\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (value !== undefined) kept[key] = value;\n\t}\n\treturn kept;\n}\n\n/** The `id` of a user reference, a subject or an object — or nothing. */\nexport function idOf(value: unknown): string | undefined {\n\tif (typeof value === 'string') return value;\n\tif (typeof value === 'object' && value !== null && 'id' in value) {\n\t\tconst id: unknown = value.id;\n\t\tif (typeof id === 'string') return id;\n\t}\n\treturn undefined;\n}\n\n/** The `type` of a subject or an object — or nothing. */\nexport function typeOf(value: unknown): string | undefined {\n\tif (typeof value === 'object' && value !== null && 'type' in value) {\n\t\tconst type: unknown = value.type;\n\t\tif (typeof type === 'string') return type;\n\t}\n\treturn undefined;\n}\n\n/** How a traced call ended, for what is written after it. */\nexport type Outcome =\n\t| { readonly ok: true; readonly value: unknown }\n\t| { readonly ok: false; readonly refusal: JanusError };\n\n/**\n * Runs `call` in a span. **A refusal is an answer**: the span ends `ok`, with\n * `janus.refusal` set to its code, and the error is rethrown once the span is\n * over. A failure is rethrown inside it, so the span is `error`, with the\n * store slot and operation a `StoreFailure` names.\n *\n * `after` sees the outcome inside the span, outside the flow's `try`, and a\n * throw from it is dropped: telemetry never changes what a flow answers.\n */\nexport async function traced<T>(\n\tname: string,\n\tfields: Fields,\n\tcall: () => Promise<T>,\n\tafter: (scope: SpanScope, outcome: Outcome) => void,\n): Promise<T> {\n\tconst ended = await span(name, { attributes: fields }, async (scope) => {\n\t\tlet outcome: { ok: true; value: T } | { ok: false; refusal: JanusError };\n\t\ttry {\n\t\t\toutcome = { ok: true, value: await call() };\n\t\t} catch (error) {\n\t\t\tif (error instanceof JanusError) {\n\t\t\t\tscope.attribute('janus.error.code', error.code);\n\t\t\t\tif (error.slot !== undefined)\n\t\t\t\t\tscope.attribute('janus.store.slot', error.slot);\n\t\t\t\tif (error.operation !== undefined) {\n\t\t\t\t\tscope.attribute('janus.store.operation', error.operation);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!isRefusal(error)) throw error;\n\t\t\tscope.attribute('janus.refusal', error.code);\n\t\t\toutcome = { ok: false, refusal: error };\n\t\t}\n\t\ttry {\n\t\t\tafter(scope, outcome);\n\t\t} catch {\n\t\t\t// A signal is never worth a flow's answer.\n\t\t}\n\t\treturn outcome;\n\t});\n\tif (!ended.ok) throw ended.refusal;\n\treturn ended.value;\n}\n",
6
- "import { createLogger, event } from '@nxgt/telemetry';\nimport type { Outcome } from '../traced';\nimport type { Call } from './call';\nimport {\n\targumentUser,\n\tprovedOf,\n\trecoveryCodesLeft,\n\trefusalFields,\n\tsecondFactorFields,\n\tsessionUserFields,\n\tsignedInFields,\n\tstatusOf,\n\tuserFields,\n\tviaOf,\n} from './fields';\nimport { mailRequest } from './mail';\n\nconst log = createLogger('@nxgt/janus');\n\n/** The events a security review reads: who signed in, out, up, stepped up, and what changed. */\nconst events = {\n\tsignedUp: event('janus.signUp'),\n\tsignedIn: event('janus.signIn'),\n\tsignInRefused: event('janus.signIn.refused'),\n\tsignInThrottled: event('janus.signIn.throttled'),\n\tsecondFactorAsked: event('janus.signIn.secondFactor'),\n\tsignInCodeSent: event('janus.signInCode.sent'),\n\tmagicLinkSent: event('janus.magicLink.sent'),\n\tstepUpAsked: event('janus.stepUp.asked'),\n\tstepUpConfirmed: event('janus.stepUp.confirmed'),\n\tstepUpRefused: event('janus.stepUp.refused'),\n\tsecondFactorEnrolled: event('janus.secondFactor.enrolled'),\n\tsecondFactorActivated: event('janus.secondFactor.activated'),\n\tsecondFactorDisabled: event('janus.secondFactor.disabled'),\n\trecoveryCodesRegenerated: event(\n\t\t'janus.secondFactor.recoveryCodesRegenerated',\n\t),\n\tsignedOut: event('janus.signOut'),\n\tsignedOutEverywhere: event('janus.signOutEverywhere'),\n\tuserDeleted: event('janus.user.deleted'),\n\tuserActivated: event('janus.user.activated'),\n\tuserDeactivated: event('janus.user.deactivated'),\n\tpasswordChanged: event('janus.password.changed'),\n\tpasswordSet: event('janus.password.set'),\n\tpasswordReset: event('janus.password.reset'),\n\temailVerified: event('janus.email.verified'),\n};\n\n/**\n * What a sign-in other than the password's writes once answered — an\n * e-mailed code or link, or the application's own proof — marked, so an\n * alert on refusals can tell one from another and from a password: a\n * refusal, the second factor asked for, or a sign-in.\n */\nfunction markedSignIn(mark: string): (call: Call, outcome: Outcome) => void {\n\treturn (call, outcome) => {\n\t\tif (!outcome.ok) {\n\t\t\tlog.warn(\n\t\t\t\tevents.signInRefused({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t[mark]: true,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (statusOf(outcome.value) === 'secondFactor') {\n\t\t\tlog.info(\n\t\t\t\tevents.secondFactorAsked(secondFactorFields(call, outcome.value)),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.info(\n\t\t\t\tevents.signedIn({\n\t\t\t\t\t...signedInFields(call, outcome.value),\n\t\t\t\t\t[mark]: true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t};\n}\n\n/**\n * What a sign-in code or link issued writes: `sent`, with its user — only\n * when one was issued, since `null` is nobody and says nothing.\n */\nfunction issued(\n\tsent: typeof events.signInCodeSent,\n): (call: Call, outcome: Outcome) => void {\n\treturn (call, outcome) => {\n\t\tif (outcome.ok && outcome.value !== null) {\n\t\t\tlog.info(sent(userFields(call, outcome.value)));\n\t\t}\n\t};\n}\n\n/**\n * What each flow writes once it answered. Nothing here reads a login, an\n * e-mail, a password or a token: only user types, ids, codes and reasons.\n */\nexport const WRITTEN: Readonly<\n\tRecord<string, (call: Call, outcome: Outcome) => void>\n> = {\n\tsignUp: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.signedUp(userFields(call, outcome.value)));\n\t},\n\tsignIn: (call, outcome) => {\n\t\tif (!outcome.ok && outcome.refusal.reason === 'throttled') {\n\t\t\t// Too many passwords at one login: no password was compared.\n\t\t\tlog.warn(\n\t\t\t\tevents.signInThrottled({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t'janus.signIn.retryAfter': outcome.refusal.retryAfter,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (!outcome.ok) {\n\t\t\tlog.warn(events.signInRefused(refusalFields(call, outcome.refusal)));\n\t\t} else if (statusOf(outcome.value) === 'secondFactor') {\n\t\t\t// The password was right; the session waits for a code.\n\t\t\tlog.info(\n\t\t\t\tevents.secondFactorAsked(secondFactorFields(call, outcome.value)),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.info(events.signedIn(signedInFields(call, outcome.value)));\n\t\t}\n\t},\n\t'signInCode.request': mailRequest(issued(events.signInCodeSent)),\n\t// In two steps: prepare is counted, and send issues — what request writes.\n\t'signInCode.prepare': mailRequest(),\n\t'signInCode.prepare.send': issued(events.signInCodeSent),\n\t'signInCode.confirm': markedSignIn('janus.signIn.code'),\n\t'magicLink.request': mailRequest(issued(events.magicLinkSent)),\n\t'magicLink.prepare': mailRequest(),\n\t'magicLink.prepare.send': issued(events.magicLinkSent),\n\t'magicLink.confirm': markedSignIn('janus.signIn.magicLink'),\n\t// No credential checked here: the application carried the proof.\n\tsignInProven: markedSignIn('janus.signIn.proven'),\n\t'stepUp.request': mailRequest((call, outcome) => {\n\t\t// Which code confirms it — e-mailed, or from the app — never the code.\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.stepUpAsked({\n\t\t\t\t\t...userFields(call, outcome.value),\n\t\t\t\t\t'janus.stepUp.via': viaOf(outcome.value),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}),\n\t'stepUp.confirm': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(events.stepUpConfirmed(sessionUserFields(call, outcome.value)));\n\t\t} else {\n\t\t\tlog.warn(events.stepUpRefused(refusalFields(call, outcome.refusal)));\n\t\t}\n\t},\n\t'secondFactor.confirm': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.signedIn({\n\t\t\t\t\t...signedInFields(call, outcome.value),\n\t\t\t\t\t'janus.signIn.secondFactor': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.warn(events.signInRefused(refusalFields(call, outcome.refusal)));\n\t\t}\n\t},\n\t'secondFactor.recover': (call, outcome) => {\n\t\t// Marked, so an alert can watch sign-ins made without the phone.\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.signedIn({\n\t\t\t\t\t...signedInFields(call, outcome.value),\n\t\t\t\t\t'janus.signIn.recoveryCode': true,\n\t\t\t\t\t'janus.secondFactor.recoveryCodesLeft': recoveryCodesLeft(\n\t\t\t\t\t\toutcome.value,\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.warn(\n\t\t\t\tevents.signInRefused({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t'janus.signIn.recoveryCode': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t},\n\t'secondFactor.regenerateRecoveryCodes': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(events.recoveryCodesRegenerated(argumentUser(call)));\n\t\t}\n\t},\n\t'secondFactor.enroll': (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.secondFactorEnrolled(argumentUser(call)));\n\t},\n\t'secondFactor.activate': (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.secondFactorActivated(argumentUser(call)));\n\t},\n\t'secondFactor.disable': (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.secondFactorDisabled(argumentUser(call)));\n\t},\n\tsignOut: (_call, outcome) => {\n\t\tif (outcome.ok && outcome.value === true) log.info(events.signedOut());\n\t},\n\tsignOutEverywhere: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.signedOutEverywhere(argumentUser(call)));\n\t},\n\tdelete: (call, outcome) => {\n\t\tif (outcome.ok && outcome.value === true) {\n\t\t\tlog.info(events.userDeleted(argumentUser(call)));\n\t\t}\n\t},\n\tsetActive: (call, outcome) => {\n\t\tif (!outcome.ok) return;\n\t\tconst written =\n\t\t\tcall.args[1] === true ? events.userActivated : events.userDeactivated;\n\t\tlog.info(written(argumentUser(call)));\n\t},\n\tchangePassword: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.passwordChanged(argumentUser(call)));\n\t},\n\tsetPassword: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.passwordSet(argumentUser(call)));\n\t},\n\t'resetPassword.request': mailRequest(),\n\t'resetPassword.prepare': mailRequest(),\n\t'verifyEmail.send': mailRequest(),\n\t'resetPassword.confirm': (call, outcome) => {\n\t\tif (outcome.ok)\n\t\t\tlog.info(events.passwordReset(userFields(call, outcome.value)));\n\t},\n\t'verifyEmail.confirm': (call, outcome) => {\n\t\tif (outcome.ok)\n\t\t\tlog.info(events.emailVerified(userFields(call, outcome.value)));\n\t},\n\t// Only when it proved: an e-mail already proved changed nothing.\n\t'verifyEmail.proveFirst': (call, outcome) => {\n\t\tif (outcome.ok && provedOf(outcome.value)) {\n\t\t\tlog.info(\n\t\t\t\tevents.emailVerified({\n\t\t\t\t\t...userFields(call, outcome.value),\n\t\t\t\t\t'janus.email.proveFirst': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t},\n};\n",
7
6
  "import type { JanusError } from '@nxgt/janus';\nimport { type Fields, fieldsOf, idOf, typeOf } from '../traced';\nimport type { Call } from './call';\n\n/** The user an answer names — `{ user }` or the user itself — as fields. */\nexport function userFields(call: Call, value: unknown): Fields {\n\tconst user =\n\t\ttypeof value === 'object' && value !== null && 'user' in value\n\t\t\t? value.user\n\t\t\t: value;\n\treturn fieldsOf({\n\t\t'janus.user.type': typeOf(user) ?? call.userType,\n\t\t'user.id': idOf(user),\n\t});\n}\n\n/**\n * A session opened: the user, and `janus.signIn.newDevice` when the answer\n * says the device was new to them — never the device token itself.\n */\nexport function signedInFields(call: Call, value: unknown): Fields {\n\tconst newDevice =\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'newDevice' in value &&\n\t\tvalue.newDevice === true;\n\treturn {\n\t\t...userFields(call, value),\n\t\t...(newDevice ? { 'janus.signIn.newDevice': true } : {}),\n\t};\n}\n\n/** Whose sign-in waits for a code: the `userId` the answer carries. */\nexport function secondFactorFields(call: Call, value: unknown): Fields {\n\tconst userId =\n\t\ttypeof value === 'object' && value !== null && 'userId' in value\n\t\t\t? value.userId\n\t\t\t: undefined;\n\treturn fieldsOf({\n\t\t'janus.user.type': call.userType,\n\t\t'user.id': typeof userId === 'string' ? userId : undefined,\n\t});\n}\n\n/** The user a flow was called for: its first argument. */\nexport function argumentUser(call: Call): Fields {\n\treturn fieldsOf({\n\t\t'janus.user.type': call.userType,\n\t\t'user.id': idOf(call.args[0]),\n\t});\n}\n\n/**\n * Why a sign-in was refused: the code, the reason, the user when one holds\n * the login, and what a challenge has left of its attempts.\n */\nexport function refusalFields(call: Call, refusal: JanusError): Fields {\n\treturn fieldsOf({\n\t\t'janus.user.type': refusal.userType ?? call.userType,\n\t\t'janus.refusal': refusal.code,\n\t\t'janus.refusal.reason': refusal.reason,\n\t\t'janus.secondFactor.attemptsLeft': refusal.attemptsLeft,\n\t\t'user.id': refusal.userId,\n\t});\n}\n\n/** `signIn`'s `status`, when the answer has one. */\nexport function statusOf(value: unknown): string | undefined {\n\treturn typeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'status' in value &&\n\t\ttypeof value.status === 'string'\n\t\t? value.status\n\t\t: undefined;\n}\n\n/** How many recovery codes `secondFactor.recover` left: a count, never a code. */\nexport function recoveryCodesLeft(value: unknown): number | undefined {\n\treturn typeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'recoveryCodesLeft' in value &&\n\t\ttypeof value.recoveryCodesLeft === 'number'\n\t\t? value.recoveryCodesLeft\n\t\t: undefined;\n}\n\n/**\n * The user a session names — what `stepUp.confirm` answers — by its\n * `userId`: the session's own id is not the user's.\n */\nexport function sessionUserFields(call: Call, value: unknown): Fields {\n\tconst userId =\n\t\ttypeof value === 'object' && value !== null && 'userId' in value\n\t\t\t? value.userId\n\t\t\t: undefined;\n\treturn fieldsOf({\n\t\t'janus.user.type': call.userType,\n\t\t'user.id': typeof userId === 'string' ? userId : undefined,\n\t});\n}\n\n/** How a step-up is confirmed — `email` or `secondFactor` — when the answer says. */\nexport function viaOf(value: unknown): string | undefined {\n\treturn typeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'via' in value &&\n\t\ttypeof value.via === 'string'\n\t\t? value.via\n\t\t: undefined;\n}\n\n/** `verifyEmail.proveFirst`'s `proved`: whether the call proved the e-mail. */\nexport function provedOf(value: unknown): boolean {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'proved' in value &&\n\t\tvalue.proved === true\n\t);\n}\n",
7
+ "import { createLogger, event } from '@nxgt/telemetry';\nimport type { Outcome } from '../traced';\nimport type { Call } from './call';\nimport {\n\trefusalFields,\n\tsecondFactorFields,\n\tsignedInFields,\n\tstatusOf,\n\tuserFields,\n} from './fields';\n\nexport const log = createLogger('@nxgt/janus');\n\n/** The events a security review reads: who signed in, out, up, stepped up, and what changed. */\nexport const events = {\n\tsignedUp: event('janus.signUp'),\n\tsignedIn: event('janus.signIn'),\n\tsignInRefused: event('janus.signIn.refused'),\n\tsignInThrottled: event('janus.signIn.throttled'),\n\tsecondFactorAsked: event('janus.signIn.secondFactor'),\n\tsignInCodeSent: event('janus.signInCode.sent'),\n\tmagicLinkSent: event('janus.magicLink.sent'),\n\tstepUpAsked: event('janus.stepUp.asked'),\n\tstepUpConfirmed: event('janus.stepUp.confirmed'),\n\tstepUpRefused: event('janus.stepUp.refused'),\n\tsecondFactorEnrolled: event('janus.secondFactor.enrolled'),\n\tsecondFactorActivated: event('janus.secondFactor.activated'),\n\tsecondFactorDisabled: event('janus.secondFactor.disabled'),\n\trecoveryCodesRegenerated: event(\n\t\t'janus.secondFactor.recoveryCodesRegenerated',\n\t),\n\tsignedOut: event('janus.signOut'),\n\tsignedOutEverywhere: event('janus.signOutEverywhere'),\n\tuserDeleted: event('janus.user.deleted'),\n\tuserActivated: event('janus.user.activated'),\n\tuserDeactivated: event('janus.user.deactivated'),\n\tpasswordChanged: event('janus.password.changed'),\n\tpasswordSet: event('janus.password.set'),\n\tpasswordReset: event('janus.password.reset'),\n\temailVerified: event('janus.email.verified'),\n\temailChanged: event('janus.email.changed'),\n};\n\n/**\n * What a sign-in other than the password's writes once answered — an\n * e-mailed code or link, or the application's own proof — marked, so an\n * alert on refusals can tell one from another and from a password: a\n * refusal, the second factor asked for, or a sign-in.\n */\nexport function markedSignIn(\n\tmark: string,\n): (call: Call, outcome: Outcome) => void {\n\treturn (call, outcome) => {\n\t\tif (!outcome.ok) {\n\t\t\tlog.warn(\n\t\t\t\tevents.signInRefused({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t[mark]: true,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (statusOf(outcome.value) === 'secondFactor') {\n\t\t\tlog.info(\n\t\t\t\tevents.secondFactorAsked(secondFactorFields(call, outcome.value)),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.info(\n\t\t\t\tevents.signedIn({\n\t\t\t\t\t...signedInFields(call, outcome.value),\n\t\t\t\t\t[mark]: true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t};\n}\n\n/**\n * What a sign-in code or link issued writes: `sent`, with its user — only\n * when one was issued, since `null` is nobody and says nothing.\n */\nexport function issued(\n\tsent: typeof events.signInCodeSent,\n): (call: Call, outcome: Outcome) => void {\n\treturn (call, outcome) => {\n\t\tif (outcome.ok && outcome.value !== null) {\n\t\t\tlog.info(sent(userFields(call, outcome.value)));\n\t\t}\n\t};\n}\n",
8
8
  "import { createLogger, event } from '@nxgt/telemetry';\nimport type { Outcome } from '../traced';\nimport type { Call } from './call';\nimport { refusalFields } from './fields';\n\nconst log = createLogger('@nxgt/janus');\n\n/** Too many e-mails asked for one address, or one user, in a window. */\nconst mailThrottled = event('janus.mail.throttled');\n\ntype Written = (call: Call, outcome: Outcome) => void;\n\n/**\n * What a request that hands out something to e-mail writes: a\n * `janus.mail.throttled` warning when it was refused past its limit — with\n * the flow and the seconds to wait, never the address — and otherwise what\n * `written` writes, if anything.\n */\nexport function mailRequest(written?: Written): Written {\n\treturn (call, outcome) => {\n\t\tif (!outcome.ok && outcome.refusal.code === 'MAIL_THROTTLED') {\n\t\t\t// Nothing was issued, so nothing is sent.\n\t\t\tlog.warn(\n\t\t\t\tmailThrottled({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t'janus.mail.flow': call.flow,\n\t\t\t\t\t...(outcome.refusal.retryAfter === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { 'janus.mail.retryAfter': outcome.refusal.retryAfter }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\twritten?.(call, outcome);\n\t};\n}\n",
9
- "import type { SpanScope } from '@nxgt/telemetry';\nimport { fieldsOf, type Outcome, traced } from '../traced';\nimport type { Call } from './call';\nimport { WRITTEN } from './events';\nimport { sessionUserFields, statusOf, userFields } from './fields';\n\n/** What a span learns from an answer: whose it is, never what it holds. */\nfunction answered(scope: SpanScope, call: Call, outcome: Outcome): void {\n\tif (outcome.ok) {\n\t\t// A session is answered by `stepUp.confirm`: its user is its `userId`.\n\t\tconst fields =\n\t\t\tcall.flow === 'stepUp.confirm'\n\t\t\t\t? sessionUserFields(call, outcome.value)\n\t\t\t\t: userFields(call, outcome.value);\n\t\tfor (const [key, value] of Object.entries(fields))\n\t\t\tscope.attribute(key, value);\n\t\tconst value = outcome.value;\n\t\tif (typeof value === 'object' && value !== null && 'renewed' in value) {\n\t\t\tscope.attribute('janus.session.renewed', value.renewed === true);\n\t\t}\n\t\tconst status = SIGN_INS.has(call.flow) ? statusOf(value) : undefined;\n\t\tif (status !== undefined) scope.attribute('janus.signIn.status', status);\n\t}\n\tWRITTEN[call.flow]?.(call, outcome);\n}\n\n/** The flows whose answer is a sign-in's: a session, or a second factor asked for. */\nconst SIGN_INS = new Set([\n\t'signIn',\n\t'signInCode.confirm',\n\t'magicLink.confirm',\n\t'signInProven',\n]);\n\n/** `cookie` answers synchronously: it is the one part of `janus()` not traced. */\nconst UNTRACED = new Set(['cookie']);\n\n/** Which user type and flow a path names: `['patient', 'signIn']`. */\ntype Split = (path: readonly string[]) => {\n\treadonly userType: string | undefined;\n\treadonly flow: readonly string[];\n};\n\n/**\n * A traced copy of `target`, frozen: its functions traced, its plain objects —\n * a user type's flows, `verifyEmail` — copied the same way. A copy rather\n * than a Proxy, which may not answer differently for a frozen property.\n */\nexport function traceObject<O extends object>(\n\ttarget: O,\n\tpath: readonly string[],\n\tsplit: Split,\n): O {\n\tconst copy: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(target)) {\n\t\tif (UNTRACED.has(key)) {\n\t\t\tcopy[key] = value;\n\t\t} else if (typeof value === 'function') {\n\t\t\tcopy[key] = traceFunction(\n\t\t\t\tvalue as (...args: unknown[]) => unknown,\n\t\t\t\ttarget,\n\t\t\t\t[...path, key],\n\t\t\t\tsplit,\n\t\t\t);\n\t\t} else if (\n\t\t\ttypeof value === 'object' &&\n\t\t\tvalue !== null &&\n\t\t\t!Array.isArray(value)\n\t\t) {\n\t\t\tcopy[key] = traceObject(value, [...path, key], split);\n\t\t} else {\n\t\t\tcopy[key] = value;\n\t\t}\n\t}\n\treturn Object.freeze(copy) as O;\n}\n\nfunction traceFunction(\n\tfn: (...args: unknown[]) => unknown,\n\tself: object,\n\tpath: readonly string[],\n\tsplit: Split,\n): (...args: unknown[]) => Promise<unknown> {\n\tconst { userType, flow } = split(path);\n\tconst name = `janus.${path.join('.')}`;\n\tconst fields = fieldsOf({ 'janus.user.type': userType });\n\treturn (...args) => {\n\t\tconst call: Call = { flow: flow.join('.'), userType, args };\n\t\tconst answer = traced(\n\t\t\tname,\n\t\t\tfields,\n\t\t\t() => Promise.resolve(fn.apply(self, args)),\n\t\t\t(scope, outcome) => answered(scope, call, outcome),\n\t\t);\n\t\treturn path.at(-1) === 'prepare'\n\t\t\t? answer.then((value) => tracePrepared(value, path, split))\n\t\t\t: answer;\n\t};\n}\n\n/**\n * What `prepare` answers — `magicLink.prepare(email)` — traced in turn, so\n * its `send()` is a span of its own, `janus.magicLink.prepare.send`, and\n * writes what `request` writes once it issued. Anything else is answered\n * as it came.\n */\nfunction tracePrepared(\n\tvalue: unknown,\n\tpath: readonly string[],\n\tsplit: Split,\n): unknown {\n\treturn typeof value === 'object' && value !== null\n\t\t? traceObject(value, path, split)\n\t\t: value;\n}\n",
9
+ "import type { Outcome } from '../traced';\nimport type { Call } from './call';\nimport { events, issued, log, markedSignIn } from './events';\nimport {\n\targumentUser,\n\tprovedOf,\n\trecoveryCodesLeft,\n\trefusalFields,\n\tsecondFactorFields,\n\tsessionUserFields,\n\tsignedInFields,\n\tstatusOf,\n\tuserFields,\n\tviaOf,\n} from './fields';\nimport { mailRequest } from './mail';\n\n/** The name `update`'s patch gives the e-mail, the field name janus defaults to. */\nconst EMAIL_FIELD = 'email';\n\n/** Whether `update`'s patch (its second argument) names the e-mail field. */\nfunction patchNamesEmail(call: Call): boolean {\n\tconst patch = call.args[1];\n\treturn (\n\t\ttypeof patch === 'object' &&\n\t\tpatch !== null &&\n\t\t(patch as Record<string, unknown>)[EMAIL_FIELD] !== undefined\n\t);\n}\n\n/**\n * What each flow writes once it answered. Nothing here reads a login, an\n * e-mail, a password or a token: only user types, ids, codes and reasons.\n */\nexport const WRITTEN: Readonly<\n\tRecord<string, (call: Call, outcome: Outcome) => void>\n> = {\n\tsignUp: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.signedUp(userFields(call, outcome.value)));\n\t},\n\tsignIn: (call, outcome) => {\n\t\tif (!outcome.ok && outcome.refusal.reason === 'throttled') {\n\t\t\t// Too many passwords at one login: no password was compared.\n\t\t\tlog.warn(\n\t\t\t\tevents.signInThrottled({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t'janus.signIn.retryAfter': outcome.refusal.retryAfter,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (!outcome.ok) {\n\t\t\tlog.warn(events.signInRefused(refusalFields(call, outcome.refusal)));\n\t\t} else if (statusOf(outcome.value) === 'secondFactor') {\n\t\t\t// The password was right; the session waits for a code.\n\t\t\tlog.info(\n\t\t\t\tevents.secondFactorAsked(secondFactorFields(call, outcome.value)),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.info(events.signedIn(signedInFields(call, outcome.value)));\n\t\t}\n\t},\n\t'signInCode.request': mailRequest(issued(events.signInCodeSent)),\n\t// In two steps: prepare is counted, and send issues — what request writes.\n\t'signInCode.prepare': mailRequest(),\n\t'signInCode.prepare.send': issued(events.signInCodeSent),\n\t'signInCode.confirm': markedSignIn('janus.signIn.code'),\n\t'magicLink.request': mailRequest(issued(events.magicLinkSent)),\n\t'magicLink.prepare': mailRequest(),\n\t'magicLink.prepare.send': issued(events.magicLinkSent),\n\t'magicLink.confirm': markedSignIn('janus.signIn.magicLink'),\n\t// No credential checked here: the application carried the proof.\n\tsignInProven: markedSignIn('janus.signIn.proven'),\n\t'stepUp.request': mailRequest((call, outcome) => {\n\t\t// Which code confirms it — e-mailed, or from the app — never the code.\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.stepUpAsked({\n\t\t\t\t\t...userFields(call, outcome.value),\n\t\t\t\t\t'janus.stepUp.via': viaOf(outcome.value),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}),\n\t'stepUp.confirm': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(events.stepUpConfirmed(sessionUserFields(call, outcome.value)));\n\t\t} else {\n\t\t\tlog.warn(events.stepUpRefused(refusalFields(call, outcome.refusal)));\n\t\t}\n\t},\n\t'secondFactor.confirm': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.signedIn({\n\t\t\t\t\t...signedInFields(call, outcome.value),\n\t\t\t\t\t'janus.signIn.secondFactor': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.warn(events.signInRefused(refusalFields(call, outcome.refusal)));\n\t\t}\n\t},\n\t'secondFactor.recover': (call, outcome) => {\n\t\t// Marked, so an alert can watch sign-ins made without the phone.\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.signedIn({\n\t\t\t\t\t...signedInFields(call, outcome.value),\n\t\t\t\t\t'janus.signIn.recoveryCode': true,\n\t\t\t\t\t'janus.secondFactor.recoveryCodesLeft': recoveryCodesLeft(\n\t\t\t\t\t\toutcome.value,\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.warn(\n\t\t\t\tevents.signInRefused({\n\t\t\t\t\t...refusalFields(call, outcome.refusal),\n\t\t\t\t\t'janus.signIn.recoveryCode': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t},\n\t'secondFactor.regenerateRecoveryCodes': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(events.recoveryCodesRegenerated(argumentUser(call)));\n\t\t}\n\t},\n\t'secondFactor.enroll': (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.secondFactorEnrolled(argumentUser(call)));\n\t},\n\t'secondFactor.activate': (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.secondFactorActivated(argumentUser(call)));\n\t},\n\t'secondFactor.disable': (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.secondFactorDisabled(argumentUser(call)));\n\t},\n\tsignOut: (_call, outcome) => {\n\t\tif (outcome.ok && outcome.value === true) log.info(events.signedOut());\n\t},\n\tsignOutEverywhere: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.signedOutEverywhere(argumentUser(call)));\n\t},\n\tdelete: (call, outcome) => {\n\t\tif (outcome.ok && outcome.value === true) {\n\t\t\tlog.info(events.userDeleted(argumentUser(call)));\n\t\t}\n\t},\n\tsetActive: (call, outcome) => {\n\t\tif (!outcome.ok) return;\n\t\tconst written =\n\t\t\tcall.args[1] === true ? events.userActivated : events.userDeactivated;\n\t\tlog.info(written(argumentUser(call)));\n\t},\n\tchangePassword: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.passwordChanged(argumentUser(call)));\n\t},\n\tsetPassword: (call, outcome) => {\n\t\tif (outcome.ok) log.info(events.passwordSet(argumentUser(call)));\n\t},\n\t'resetPassword.request': mailRequest(),\n\t'resetPassword.prepare': mailRequest(),\n\t'verifyEmail.send': mailRequest(),\n\t'changeEmail.send': mailRequest(),\n\t'resetPassword.confirm': (call, outcome) => {\n\t\tif (outcome.ok)\n\t\t\tlog.info(events.passwordReset(userFields(call, outcome.value)));\n\t},\n\t'verifyEmail.confirm': (call, outcome) => {\n\t\tif (outcome.ok)\n\t\t\tlog.info(events.emailVerified(userFields(call, outcome.value)));\n\t},\n\t// One event, not two: the move is the proof; no address is written.\n\t'changeEmail.confirm': (call, outcome) => {\n\t\tif (outcome.ok) {\n\t\t\tlog.info(\n\t\t\t\tevents.emailChanged({\n\t\t\t\t\t...userFields(call, outcome.value),\n\t\t\t\t\t'janus.email.proved': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t},\n\t// Moved without proof, whatever the application decided to trust. Read\n\t// from the patch: it names the `email` field (the default name; telemetry\n\t// does not know a renamed one), even when the value turns out unchanged.\n\tupdate: (call, outcome) => {\n\t\tif (outcome.ok && patchNamesEmail(call)) {\n\t\t\tlog.info(\n\t\t\t\tevents.emailChanged({\n\t\t\t\t\t...userFields(call, outcome.value),\n\t\t\t\t\t'janus.email.proved': false,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t},\n\t// Only when it proved: an e-mail already proved changed nothing.\n\t'verifyEmail.proveFirst': (call, outcome) => {\n\t\tif (outcome.ok && provedOf(outcome.value)) {\n\t\t\tlog.info(\n\t\t\t\tevents.emailVerified({\n\t\t\t\t\t...userFields(call, outcome.value),\n\t\t\t\t\t'janus.email.proveFirst': true,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t},\n};\n",
10
+ "import type { SpanScope } from '@nxgt/telemetry';\nimport { fieldsOf, type Outcome, traced } from '../traced';\nimport type { Call } from './call';\nimport { sessionUserFields, statusOf, userFields } from './fields';\nimport { WRITTEN } from './written';\n\n/** What a span learns from an answer: whose it is, never what it holds. */\nfunction answered(scope: SpanScope, call: Call, outcome: Outcome): void {\n\tif (outcome.ok) {\n\t\t// A session is answered by `stepUp.confirm`: its user is its `userId`.\n\t\tconst fields =\n\t\t\tcall.flow === 'stepUp.confirm'\n\t\t\t\t? sessionUserFields(call, outcome.value)\n\t\t\t\t: userFields(call, outcome.value);\n\t\tfor (const [key, value] of Object.entries(fields))\n\t\t\tscope.attribute(key, value);\n\t\tconst value = outcome.value;\n\t\tif (typeof value === 'object' && value !== null && 'renewed' in value) {\n\t\t\tscope.attribute('janus.session.renewed', value.renewed === true);\n\t\t}\n\t\tconst status = SIGN_INS.has(call.flow) ? statusOf(value) : undefined;\n\t\tif (status !== undefined) scope.attribute('janus.signIn.status', status);\n\t}\n\tWRITTEN[call.flow]?.(call, outcome);\n}\n\n/** The flows whose answer is a sign-in's: a session, or a second factor asked for. */\nconst SIGN_INS = new Set([\n\t'signIn',\n\t'signInCode.confirm',\n\t'magicLink.confirm',\n\t'signInProven',\n]);\n\n/** `cookie` answers synchronously: it is the one part of `janus()` not traced. */\nconst UNTRACED = new Set(['cookie']);\n\n/** Which user type and flow a path names: `['patient', 'signIn']`. */\ntype Split = (path: readonly string[]) => {\n\treadonly userType: string | undefined;\n\treadonly flow: readonly string[];\n};\n\n/**\n * A traced copy of `target`, frozen: its functions traced, its plain objects —\n * a user type's flows, `verifyEmail` — copied the same way. A copy rather\n * than a Proxy, which may not answer differently for a frozen property.\n */\nexport function traceObject<O extends object>(\n\ttarget: O,\n\tpath: readonly string[],\n\tsplit: Split,\n): O {\n\tconst copy: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(target)) {\n\t\tif (UNTRACED.has(key)) {\n\t\t\tcopy[key] = value;\n\t\t} else if (typeof value === 'function') {\n\t\t\tcopy[key] = traceFunction(\n\t\t\t\tvalue as (...args: unknown[]) => unknown,\n\t\t\t\ttarget,\n\t\t\t\t[...path, key],\n\t\t\t\tsplit,\n\t\t\t);\n\t\t} else if (\n\t\t\ttypeof value === 'object' &&\n\t\t\tvalue !== null &&\n\t\t\t!Array.isArray(value)\n\t\t) {\n\t\t\tcopy[key] = traceObject(value, [...path, key], split);\n\t\t} else {\n\t\t\tcopy[key] = value;\n\t\t}\n\t}\n\treturn Object.freeze(copy) as O;\n}\n\nfunction traceFunction(\n\tfn: (...args: unknown[]) => unknown,\n\tself: object,\n\tpath: readonly string[],\n\tsplit: Split,\n): (...args: unknown[]) => Promise<unknown> {\n\tconst { userType, flow } = split(path);\n\tconst name = `janus.${path.join('.')}`;\n\tconst fields = fieldsOf({ 'janus.user.type': userType });\n\treturn (...args) => {\n\t\tconst call: Call = { flow: flow.join('.'), userType, args };\n\t\tconst answer = traced(\n\t\t\tname,\n\t\t\tfields,\n\t\t\t() => Promise.resolve(fn.apply(self, args)),\n\t\t\t(scope, outcome) => answered(scope, call, outcome),\n\t\t);\n\t\treturn path.at(-1) === 'prepare'\n\t\t\t? answer.then((value) => tracePrepared(value, path, split))\n\t\t\t: answer;\n\t};\n}\n\n/**\n * What `prepare` answers — `magicLink.prepare(email)` — traced in turn, so\n * its `send()` is a span of its own, `janus.magicLink.prepare.send`, and\n * writes what `request` writes once it issued. Anything else is answered\n * as it came.\n */\nfunction tracePrepared(\n\tvalue: unknown,\n\tpath: readonly string[],\n\tsplit: Split,\n): unknown {\n\treturn typeof value === 'object' && value !== null\n\t\t? traceObject(value, path, split)\n\t\t: value;\n}\n",
10
11
  "import { traceObject } from './trace';\n\n/** What `janus()` answers, as far as it is read: its user types and its flows. */\nexport interface JanusLike {\n\treadonly types: readonly string[];\n\treadonly authenticate: (...args: never[]) => Promise<unknown>;\n}\n\n/**\n * The same `janus()` instance, with every flow traced — a span per call,\n * named `janus.signIn` or `janus.patient.signIn` — and the events a security\n * review reads written as logs: sign-ups, sign-ins and the reason one was\n * refused, a second factor asked for, enrolled, activated or disabled, a\n * sign-in by recovery code and the codes left, recovery codes regenerated,\n * sign-outs, deleted and deactivated users, changed passwords.\n *\n * ```ts\n * const auth = instrumentJanus(janus({ users, store, hasher }));\n * ```\n *\n * **A refusal is an answer**: a wrong password leaves its span `ok`, with\n * `janus.refusal` set, and is a `janus.signIn.refused` warning — never a\n * failed span. A store that cannot answer is one, with `janus.store.slot`.\n *\n * Nothing written carries a login, an e-mail, a password, a token, a\n * challenge, a code, a TOTP secret or a session id: user types, user ids,\n * codes and reasons only.\n */\nexport function instrumentJanus<A extends JanusLike>(auth: A): A {\n\tconst types = auth.types.filter((type) => typeof type === 'string');\n\tconst only = types.length === 1 ? types[0] : undefined;\n\treturn traceObject(auth, [], (path) =>\n\t\tpath[0] !== undefined && types.includes(path[0])\n\t\t\t? { userType: path[0], flow: path.slice(1) }\n\t\t\t: { userType: only, flow: path },\n\t);\n}\n",
11
- "import { isSetOf } from '@nxgt/janus';\nimport { createLogger, event, type TelemetryEvent } from '@nxgt/telemetry';\nimport { type Fields, fieldsOf, idOf, traced, typeOf } from './traced';\n\nconst log = createLogger('@nxgt/janus');\n\n/** The audit trail of who was given what: one event per tuple written. */\nconst granted = event('janus.tuple.granted');\nconst revoked = event('janus.tuple.revoked');\n\n/**\n * A subject — a user, an object, or a subject set — as fields, read as\n * `permissions()` reads it: a value of a user type is that user, even with a\n * field named `relation`, unless `setOf()` made it.\n */\nfunction subjectFields(\n\tsubject: unknown,\n\tuserTypes: ReadonlySet<string>,\n): Fields {\n\tconst type = typeOf(subject);\n\tconst relation =\n\t\ttypeof subject === 'object' && subject !== null && 'relation' in subject\n\t\t\t? subject.relation\n\t\t\t: undefined;\n\tconst isSet =\n\t\ttypeof relation === 'string' &&\n\t\t(isSetOf(subject) || type === undefined || !userTypes.has(type));\n\treturn fieldsOf({\n\t\t'janus.subject.type': type,\n\t\t'janus.subject.id': idOf(subject),\n\t\t'janus.subject.relation': isSet ? relation : undefined,\n\t});\n}\n\nfunction objectFields(object: unknown): Fields {\n\treturn fieldsOf({\n\t\t'janus.object.type': typeOf(object),\n\t\t'janus.object.id': idOf(object),\n\t});\n}\n\n/** What `permissions()` answers, as far as it is traced. */\nexport interface PermissionsLike {\n\t/** Its user types tell a user from a subject set of the same shape. */\n\treadonly model?: { readonly subjects: readonly string[] };\n\treadonly can: (...args: never[]) => Promise<boolean>;\n\treadonly list: (...args: never[]) => Promise<unknown>;\n\treadonly grant: (...args: never[]) => Promise<unknown>;\n\treadonly revoke: (...args: never[]) => Promise<unknown>;\n}\n\ntype Traced = (...args: unknown[]) => Promise<unknown>;\n\n/** How each method of `permissions()` is traced. */\nconst tracersFor = (\n\tuserTypes: ReadonlySet<string>,\n): Readonly<Record<string, (fn: Traced) => Traced>> => ({\n\tcan:\n\t\t(fn) =>\n\t\t(...args) =>\n\t\t\ttraced(\n\t\t\t\t'janus.can',\n\t\t\t\tfieldsOf({\n\t\t\t\t\t...subjectFields(args[0], userTypes),\n\t\t\t\t\t'janus.permission': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t\t\t...objectFields(args[2]),\n\t\t\t\t}),\n\t\t\t\t() => fn(...args),\n\t\t\t\t(scope, outcome) => {\n\t\t\t\t\tif (outcome.ok)\n\t\t\t\t\t\tscope.attribute('janus.allowed', outcome.value === true);\n\t\t\t\t},\n\t\t\t),\n\tlist:\n\t\t(fn) =>\n\t\t(...args) =>\n\t\t\ttraced(\n\t\t\t\t'janus.list',\n\t\t\t\tfieldsOf({\n\t\t\t\t\t...subjectFields(args[0], userTypes),\n\t\t\t\t\t'janus.permission': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t\t\t'janus.object.type':\n\t\t\t\t\t\ttypeof args[2] === 'string' ? args[2] : undefined,\n\t\t\t\t}),\n\t\t\t\t() => fn(...args),\n\t\t\t\t(scope, outcome) => {\n\t\t\t\t\tconst page = outcome.ok ? outcome.value : undefined;\n\t\t\t\t\tif (typeof page === 'object' && page !== null && 'items' in page) {\n\t\t\t\t\t\tconst items = page.items;\n\t\t\t\t\t\tif (Array.isArray(items))\n\t\t\t\t\t\t\tscope.attribute('janus.page.items', items.length);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t),\n\tgrant: (fn) => tupleWrite('janus.grant', granted, fn, userTypes),\n\trevoke: (fn) => tupleWrite('janus.revoke', revoked, fn, userTypes),\n});\n\nfunction tupleWrite(\n\tname: string,\n\twritten: (fields: Fields) => TelemetryEvent,\n\tfn: Traced,\n\tuserTypes: ReadonlySet<string>,\n): Traced {\n\treturn (...args) => {\n\t\tconst fields = fieldsOf({\n\t\t\t...objectFields(args[0]),\n\t\t\t'janus.relation': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t...subjectFields(args[2], userTypes),\n\t\t});\n\t\treturn traced(\n\t\t\tname,\n\t\t\tfields,\n\t\t\t() => fn(...args),\n\t\t\t(_scope, outcome) => {\n\t\t\t\tif (outcome.ok) log.info(written(fields));\n\t\t\t},\n\t\t);\n\t};\n}\n\n/**\n * The same `permissions()` instance, traced: a span per `can` — its subject,\n * permission, object and whether it was allowed — and per `list`, and a\n * `janus.tuple.granted` or `janus.tuple.revoked` event for every tuple\n * written, the audit trail of who was given what.\n *\n * ```ts\n * const access = instrumentPermissions(permissions({ model, store }));\n * ```\n *\n * A denial is an answer, not a failure: `janus.allowed` is `false` and the\n * span is `ok`. A relation store that cannot answer fails the span.\n */\nexport function instrumentPermissions<A extends PermissionsLike>(access: A): A {\n\t// `permissions()` answers a frozen object, which a Proxy may not answer\n\t// differently for: the traced methods go on a copy, frozen in turn.\n\tconst copy: Record<string, unknown> = Object.fromEntries(\n\t\tObject.entries(access),\n\t);\n\tconst userTypes = new Set(access.model?.subjects ?? []);\n\tfor (const [key, tracer] of Object.entries(tracersFor(userTypes))) {\n\t\tconst method: unknown = copy[key];\n\t\tif (typeof method !== 'function') continue;\n\t\tcopy[key] = tracer((...args) =>\n\t\t\tPromise.resolve(method.apply(access, args)),\n\t\t);\n\t}\n\treturn Object.freeze(copy) as A;\n}\n"
12
+ "import { isSetOf } from '@nxgt/janus';\nimport { createLogger, event, type TelemetryEvent } from '@nxgt/telemetry';\nimport { type Fields, fieldsOf, idOf, traced, typeOf } from './traced';\n\nconst log = createLogger('@nxgt/janus');\n\n/** The audit trail of who was given what: one event per tuple written. */\nconst granted = event('janus.tuple.granted');\nconst revoked = event('janus.tuple.revoked');\n\n/**\n * A subject — a user, an object, or a subject set — as fields, read as\n * `permissions()` reads it: a value of a user type is that user, even with a\n * field named `relation`, unless `setOf()` made it.\n */\nfunction subjectFields(\n\tsubject: unknown,\n\tuserTypes: ReadonlySet<string>,\n): Fields {\n\tconst type = typeOf(subject);\n\tconst relation =\n\t\ttypeof subject === 'object' && subject !== null && 'relation' in subject\n\t\t\t? subject.relation\n\t\t\t: undefined;\n\tconst isSet =\n\t\ttypeof relation === 'string' &&\n\t\t(isSetOf(subject) || type === undefined || !userTypes.has(type));\n\treturn fieldsOf({\n\t\t'janus.subject.type': type,\n\t\t'janus.subject.id': idOf(subject),\n\t\t'janus.subject.relation': isSet ? relation : undefined,\n\t});\n}\n\nfunction objectFields(object: unknown): Fields {\n\treturn fieldsOf({\n\t\t'janus.object.type': typeOf(object),\n\t\t'janus.object.id': idOf(object),\n\t});\n}\n\n/** What `permissions()` answers, as far as it is traced. */\nexport interface PermissionsLike {\n\t/** Its user types tell a user from a subject set of the same shape. */\n\treadonly model?: { readonly subjects: readonly string[] };\n\treadonly can: (...args: never[]) => Promise<boolean>;\n\treadonly list: (...args: never[]) => Promise<unknown>;\n\treadonly grant: (...args: never[]) => Promise<unknown>;\n\treadonly revoke: (...args: never[]) => Promise<unknown>;\n\t/** Traced when present: an instance made before `replace` has none. */\n\treadonly replace?: (...args: never[]) => Promise<unknown>;\n}\n\ntype Traced = (...args: unknown[]) => Promise<unknown>;\n\n/** How each method of `permissions()` is traced. */\nconst tracersFor = (\n\tuserTypes: ReadonlySet<string>,\n): Readonly<Record<string, (fn: Traced) => Traced>> => ({\n\tcan:\n\t\t(fn) =>\n\t\t(...args) =>\n\t\t\ttraced(\n\t\t\t\t'janus.can',\n\t\t\t\tfieldsOf({\n\t\t\t\t\t...subjectFields(args[0], userTypes),\n\t\t\t\t\t'janus.permission': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t\t\t...objectFields(args[2]),\n\t\t\t\t}),\n\t\t\t\t() => fn(...args),\n\t\t\t\t(scope, outcome) => {\n\t\t\t\t\tif (outcome.ok)\n\t\t\t\t\t\tscope.attribute('janus.allowed', outcome.value === true);\n\t\t\t\t},\n\t\t\t),\n\tlist:\n\t\t(fn) =>\n\t\t(...args) =>\n\t\t\ttraced(\n\t\t\t\t'janus.list',\n\t\t\t\tfieldsOf({\n\t\t\t\t\t...subjectFields(args[0], userTypes),\n\t\t\t\t\t'janus.permission': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t\t\t'janus.object.type':\n\t\t\t\t\t\ttypeof args[2] === 'string' ? args[2] : undefined,\n\t\t\t\t}),\n\t\t\t\t() => fn(...args),\n\t\t\t\t(scope, outcome) => {\n\t\t\t\t\tconst page = outcome.ok ? outcome.value : undefined;\n\t\t\t\t\tif (typeof page === 'object' && page !== null && 'items' in page) {\n\t\t\t\t\t\tconst items = page.items;\n\t\t\t\t\t\tif (Array.isArray(items))\n\t\t\t\t\t\t\tscope.attribute('janus.page.items', items.length);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t),\n\tgrant: (fn) => tupleWrite('janus.grant', granted, fn, userTypes),\n\trevoke: (fn) => tupleWrite('janus.revoke', revoked, fn, userTypes),\n\treplace: (fn) => replaceWrite(fn),\n});\n\n/** A stored tuple as fields: its subject is a set when it names a relation. */\nfunction storedFields(tuple: unknown): Fields {\n\tif (typeof tuple !== 'object' || tuple === null) return {};\n\tconst { object, relation, subject } = tuple as Record<string, unknown>;\n\tconst setRelation =\n\t\ttypeof subject === 'object' && subject !== null && 'relation' in subject\n\t\t\t? subject.relation\n\t\t\t: undefined;\n\treturn fieldsOf({\n\t\t...objectFields(object),\n\t\t'janus.relation': typeof relation === 'string' ? relation : undefined,\n\t\t'janus.subject.type': typeOf(subject),\n\t\t'janus.subject.id': idOf(subject),\n\t\t'janus.subject.relation':\n\t\t\ttypeof setRelation === 'string' ? setRelation : undefined,\n\t});\n}\n\n/** `replace`: the object and relation on the span, an event per tuple it answers. */\nfunction replaceWrite(fn: Traced): Traced {\n\treturn (...args) =>\n\t\ttraced(\n\t\t\t'janus.replace',\n\t\t\tfieldsOf({\n\t\t\t\t...objectFields(args[0]),\n\t\t\t\t'janus.relation': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t}),\n\t\t\t() => fn(...args),\n\t\t\t(_scope, outcome) => {\n\t\t\t\tif (!outcome.ok || typeof outcome.value !== 'object') return;\n\t\t\t\tconst { added, removed } = (outcome.value ?? {}) as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>;\n\t\t\t\tfor (const [tuples, written] of [\n\t\t\t\t\t[added, granted],\n\t\t\t\t\t[removed, revoked],\n\t\t\t\t] as const) {\n\t\t\t\t\tif (!Array.isArray(tuples)) continue;\n\t\t\t\t\tfor (const tuple of tuples) log.info(written(storedFields(tuple)));\n\t\t\t\t}\n\t\t\t},\n\t\t);\n}\n\nfunction tupleWrite(\n\tname: string,\n\twritten: (fields: Fields) => TelemetryEvent,\n\tfn: Traced,\n\tuserTypes: ReadonlySet<string>,\n): Traced {\n\treturn (...args) => {\n\t\tconst fields = fieldsOf({\n\t\t\t...objectFields(args[0]),\n\t\t\t'janus.relation': typeof args[1] === 'string' ? args[1] : undefined,\n\t\t\t...subjectFields(args[2], userTypes),\n\t\t});\n\t\treturn traced(\n\t\t\tname,\n\t\t\tfields,\n\t\t\t() => fn(...args),\n\t\t\t(_scope, outcome) => {\n\t\t\t\tif (outcome.ok) log.info(written(fields));\n\t\t\t},\n\t\t);\n\t};\n}\n\n/**\n * The same `permissions()` instance, traced: a span per `can` — its subject,\n * permission, object and whether it was allowed — and per `list`, a span per\n * `grant`, `revoke` and `replace`, and a `janus.tuple.granted` or\n * `janus.tuple.revoked` event for every tuple written, the audit trail of who\n * was given what. `replace` writes one event per tuple it added and one per\n * tuple it removed.\n *\n * ```ts\n * const access = instrumentPermissions(permissions({ model, store }));\n * ```\n *\n * A denial is an answer, not a failure: `janus.allowed` is `false` and the\n * span is `ok`. A relation store that cannot answer fails the span.\n */\nexport function instrumentPermissions<A extends PermissionsLike>(access: A): A {\n\t// `permissions()` answers a frozen object, which a Proxy may not answer\n\t// differently for: the traced methods go on a copy, frozen in turn.\n\tconst copy: Record<string, unknown> = Object.fromEntries(\n\t\tObject.entries(access),\n\t);\n\tconst userTypes = new Set(access.model?.subjects ?? []);\n\tfor (const [key, tracer] of Object.entries(tracersFor(userTypes))) {\n\t\tconst method: unknown = copy[key];\n\t\tif (typeof method !== 'function') continue;\n\t\tcopy[key] = tracer((...args) =>\n\t\t\tPromise.resolve(method.apply(access, args)),\n\t\t);\n\t}\n\treturn Object.freeze(copy) as A;\n}\n"
12
13
  ],
13
- "mappings": "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",
14
- "debugId": "F208819435F0AAFC64756E2164756E21",
14
+ "mappings": "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",
15
+ "debugId": "D06ADE7604EC6B3A64756E2164756E21",
15
16
  "names": []
16
17
  }
@@ -8,12 +8,16 @@ export interface PermissionsLike {
8
8
  readonly list: (...args: never[]) => Promise<unknown>;
9
9
  readonly grant: (...args: never[]) => Promise<unknown>;
10
10
  readonly revoke: (...args: never[]) => Promise<unknown>;
11
+ /** Traced when present: an instance made before `replace` has none. */
12
+ readonly replace?: (...args: never[]) => Promise<unknown>;
11
13
  }
12
14
  /**
13
15
  * The same `permissions()` instance, traced: a span per `can` — its subject,
14
- * permission, object and whether it was allowed — and per `list`, and a
15
- * `janus.tuple.granted` or `janus.tuple.revoked` event for every tuple
16
- * written, the audit trail of who was given what.
16
+ * permission, object and whether it was allowed — and per `list`, a span per
17
+ * `grant`, `revoke` and `replace`, and a `janus.tuple.granted` or
18
+ * `janus.tuple.revoked` event for every tuple written, the audit trail of who
19
+ * was given what. `replace` writes one event per tuple it added and one per
20
+ * tuple it removed.
17
21
  *
18
22
  * ```ts
19
23
  * const access = instrumentPermissions(permissions({ model, store }));
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAyCA,4DAA4D;AAC5D,MAAM,WAAW,eAAe;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxD;AAwED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,eAAe,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAe7E"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAyCA,4DAA4D;AAC5D,MAAM,WAAW,eAAe;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D;AAsHD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,eAAe,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAe7E"}
@@ -95,11 +95,11 @@ janus.patient.signIn ok janus.refusal=CREDENTIALS_INVALID
95
95
 
96
96
  A request that sends an e-mail past its attempts — `@nxgt/janus` throttles
97
97
  `magicLink.request`, `signInCode.request`, `resetPassword.request`,
98
- `verifyEmail.send` and an e-mailed `stepUp.request` — is a
98
+ `verifyEmail.send`, `changeEmail.send` and an e-mailed `stepUp.request` — is a
99
99
  `janus.mail.throttled` warning. The span stays ok with `janus.refusal` set;
100
100
  the event names the flow and the seconds until the window ends, and never the
101
101
  address. `user.id` is there for the per-user flows (`verifyEmail.send`,
102
- `stepUp.request`) only:
102
+ `changeEmail.send`, `stepUp.request`) only:
103
103
 
104
104
  ```
105
105
  janus.magicLink.request ok janus.refusal=MAIL_THROTTLED
@@ -112,7 +112,9 @@ janus.magicLink.request ok janus.refusal=MAIL_THROTTLED
112
112
  ```
113
113
 
114
114
  `janus.tuple.granted` and `janus.tuple.revoked` are the record of who was
115
- given what:
115
+ given what: one per tuple a `grant` or `revoke` writes, and, for a `replace`
116
+ (span `janus.replace`), one `granted` per tuple it added and one `revoked` per
117
+ tuple it removed:
116
118
 
117
119
  ```
118
120
  janus.tuple.granted janus.object.type=record janus.object.id=r1 janus.relation=owners janus.subject.type=patient janus.subject.id=u1
@@ -351,6 +353,37 @@ POST /invitation/accept server
351
353
  log janus.signIn user.id=0199… janus.signIn.proven=true
352
354
  ```
353
355
 
356
+ ## A changed e-mail
357
+
358
+ `changeEmail.send` moves nobody by itself: it is counted by the mail throttle like
359
+ the other e-mail requests, so a refused one is a `janus.mail.throttled` with
360
+ `janus.mail.flow=changeEmail.send` and the `user.id`. `changeEmail.confirm`
361
+ moves the user to the new, proved address and writes `janus.email.changed`
362
+ with the `user.id` and `janus.email.proved=true`, and nothing else about the
363
+ move: neither the former nor the new address, and no `janus.email.verified`
364
+ besides, since the move is the proof. `update` writes the same event with
365
+ `janus.email.proved=false` when its patch names the `email` field, so a
366
+ move without proof is as visible as one with it. Telemetry sees only the
367
+ call's arguments and answer: the event is written for any patch that names
368
+ `email` — also one that turns out to keep the same address — and a user type
369
+ whose e-mail field is renamed is not seen on `update`, nor an `update` that
370
+ moved the address and then failed with `STORE_FAILED` while spending the
371
+ change links: the event is written for an answered call only. Whoever changes an
372
+ address takes over password resets, so this is the event to alert on:
373
+
374
+ ```
375
+ POST /email/confirm server
376
+ └─ janus.changeEmail.confirm janus.user.type=user user.id=0199…
377
+ log janus.email.changed user.id=0199…
378
+ ```
379
+
380
+ A refusal (`TOKEN_STALE`, `LOGIN_TAKEN`, a `TOKEN_*` code) writes no event;
381
+ the span records the `janus.refusal`.
382
+
383
+ ```ts
384
+ // name = 'janus.email.changed' and janus.email.proved = false — who moved to another address without proof, and when
385
+ ```
386
+
354
387
  ## A step-up
355
388
 
356
389
  A step-up — a signed-in user proving again who they are before a
package/docs/roadmap.md CHANGED
@@ -33,6 +33,18 @@ Nothing yet.
33
33
 
34
34
  Newest first; the [CHANGELOG](../CHANGELOG.md) holds every release.
35
35
 
36
+ - **Tracing `replace`, v0.13.0** — `instrumentPermissions` traces
37
+ `access.replace(object, relation, subjects)` like `grant` and `revoke`: a
38
+ `janus.replace` span with the object and `janus.relation`, and one
39
+ `janus.tuple.granted` audit event per tuple it added and one
40
+ `janus.tuple.revoked` per tuple it removed.
41
+ - **E-mail changes in the audit trail** — `changeEmail.confirm` writes
42
+ `janus.email.changed` with the `user.id` and `janus.email.proved: true`,
43
+ never an address (an `update` whose patch names `email` writes it with
44
+ `janus.email.proved: false`), and
45
+ `changeEmail.send` is counted by the mail throttle like the other e-mail
46
+ requests: `janus.mail.throttled` with `janus.mail.flow: changeEmail.send`.
47
+ Needs the `@nxgt/janus` that ships `changeEmail`.
36
48
  - **An e-mail proved on the application's proof, traced, v0.11.0** —
37
49
  `verifyEmail.proveFirst` is a span, `janus.<type>.verifyEmail.proveFirst`,
38
50
  with the `user.id` it proved, and writes `janus.email.verified` marked
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/janus-telemetry",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "Observability for @nxgt/janus on @nxgt/telemetry: a span per flow and per permission check, and the security events worth an audit trail — never a login, an e-mail, a password, a session token, a one-time token or a session id",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,13 +49,13 @@
49
49
  ]
50
50
  },
51
51
  "devDependencies": {
52
- "@nxgt/janus": "^0.22.1",
53
- "@nxgt/telemetry": "0.2.1",
52
+ "@nxgt/janus": "^0.24.0",
53
+ "@nxgt/telemetry": "0.2.2",
54
54
  "@types/bun": "^1.4.2",
55
55
  "zod": "^4.6.5"
56
56
  },
57
57
  "peerDependencies": {
58
- "@nxgt/janus": "^0.22.1",
58
+ "@nxgt/janus": "^0.24.0",
59
59
  "@nxgt/telemetry": ">=0.2.1 <1",
60
60
  "typescript": "^6.0.3 || ^7.0.0"
61
61
  }