@nxgt/janus-telemetry 0.11.1 → 0.12.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 +6 -1
- package/dist/flows/events.d.ts +38 -3
- package/dist/flows/events.d.ts.map +1 -1
- package/dist/flows/written.d.ts +8 -0
- package/dist/flows/written.d.ts.map +1 -0
- package/dist/index.js +97 -74
- package/dist/index.js.map +6 -5
- package/docs/guide/tracing.md +33 -2
- package/docs/roadmap.md +7 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -40,6 +40,10 @@ 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 |
|
|
@@ -80,7 +84,7 @@ when the flow knows them — `janus.signOut` carries neither:
|
|
|
80
84
|
| `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
85
|
| `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
86
|
| `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 |
|
|
87
|
+
| `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
88
|
| `janus.secondFactor.enrolled`, `janus.secondFactor.activated`, `janus.secondFactor.disabled` | info | `enroll`, `activate` and `disable`, with the `user.id` they were called for |
|
|
85
89
|
| `janus.secondFactor.recoveryCodesRegenerated` | info | `regenerateRecoveryCodes`, with the `user.id` it was called for — never a code |
|
|
86
90
|
| `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,6 +96,7 @@ when the flow knows them — `janus.signOut` carries neither:
|
|
|
92
96
|
| `janus.user.activated`, `janus.user.deactivated` | info | `setActive` |
|
|
93
97
|
| `janus.password.changed`, `janus.password.set`, `janus.password.reset` | info | the user's change, an admin's, a reset |
|
|
94
98
|
| `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) |
|
|
99
|
+
| `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) |
|
|
95
100
|
| `janus.tuple.granted`, `janus.tuple.revoked` | info | a tuple was written, with the object, relation and subject |
|
|
96
101
|
|
|
97
102
|
## Traps
|
package/dist/flows/events.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
* e-
|
|
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
|
|
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;
|
|
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/
|
|
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:
|
|
171
|
-
signedIn:
|
|
172
|
-
signInRefused:
|
|
173
|
-
signInThrottled:
|
|
174
|
-
secondFactorAsked:
|
|
175
|
-
signInCodeSent:
|
|
176
|
-
magicLinkSent:
|
|
177
|
-
stepUpAsked:
|
|
178
|
-
stepUpConfirmed:
|
|
179
|
-
stepUpRefused:
|
|
180
|
-
secondFactorEnrolled:
|
|
181
|
-
secondFactorActivated:
|
|
182
|
-
secondFactorDisabled:
|
|
183
|
-
recoveryCodesRegenerated:
|
|
184
|
-
signedOut:
|
|
185
|
-
signedOutEverywhere:
|
|
186
|
-
userDeleted:
|
|
187
|
-
userActivated:
|
|
188
|
-
userDeactivated:
|
|
189
|
-
passwordChanged:
|
|
190
|
-
passwordSet:
|
|
191
|
-
passwordReset:
|
|
192
|
-
emailVerified:
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
|
|
203
184
|
} else {
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
+
log.warn(events.signInRefused(refusalFields(call, outcome.refusal)));
|
|
231
237
|
} else if (statusOf(outcome.value) === "secondFactor") {
|
|
232
|
-
|
|
238
|
+
log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
|
|
233
239
|
} else {
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
+
log.info(events.stepUpConfirmed(sessionUserFields(call, outcome.value)));
|
|
257
263
|
} else {
|
|
258
|
-
|
|
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
|
-
|
|
269
|
+
log.info(events.signedIn({
|
|
264
270
|
...signedInFields(call, outcome.value),
|
|
265
271
|
"janus.signIn.secondFactor": true
|
|
266
272
|
}));
|
|
267
273
|
} else {
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
+
log.info(events.recoveryCodesRegenerated(argumentUser(call)));
|
|
288
294
|
}
|
|
289
295
|
},
|
|
290
296
|
"secondFactor.enroll": (call, outcome) => {
|
|
291
297
|
if (outcome.ok)
|
|
292
|
-
|
|
298
|
+
log.info(events.secondFactorEnrolled(argumentUser(call)));
|
|
293
299
|
},
|
|
294
300
|
"secondFactor.activate": (call, outcome) => {
|
|
295
301
|
if (outcome.ok)
|
|
296
|
-
|
|
302
|
+
log.info(events.secondFactorActivated(argumentUser(call)));
|
|
297
303
|
},
|
|
298
304
|
"secondFactor.disable": (call, outcome) => {
|
|
299
305
|
if (outcome.ok)
|
|
300
|
-
|
|
306
|
+
log.info(events.secondFactorDisabled(argumentUser(call)));
|
|
301
307
|
},
|
|
302
308
|
signOut: (_call, outcome) => {
|
|
303
309
|
if (outcome.ok && outcome.value === true)
|
|
304
|
-
|
|
310
|
+
log.info(events.signedOut());
|
|
305
311
|
},
|
|
306
312
|
signOutEverywhere: (call, outcome) => {
|
|
307
313
|
if (outcome.ok)
|
|
308
|
-
|
|
314
|
+
log.info(events.signedOutEverywhere(argumentUser(call)));
|
|
309
315
|
},
|
|
310
316
|
delete: (call, outcome) => {
|
|
311
317
|
if (outcome.ok && outcome.value === true) {
|
|
312
|
-
|
|
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
|
-
|
|
325
|
+
log.info(written(argumentUser(call)));
|
|
320
326
|
},
|
|
321
327
|
changePassword: (call, outcome) => {
|
|
322
328
|
if (outcome.ok)
|
|
323
|
-
|
|
329
|
+
log.info(events.passwordChanged(argumentUser(call)));
|
|
324
330
|
},
|
|
325
331
|
setPassword: (call, outcome) => {
|
|
326
332
|
if (outcome.ok)
|
|
327
|
-
|
|
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
|
-
|
|
341
|
+
log.info(events.passwordReset(userFields(call, outcome.value)));
|
|
335
342
|
},
|
|
336
343
|
"verifyEmail.confirm": (call, outcome) => {
|
|
337
344
|
if (outcome.ok)
|
|
338
|
-
|
|
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
|
-
|
|
365
|
+
log.info(events.emailVerified({
|
|
343
366
|
...userFields(call, outcome.value),
|
|
344
367
|
"janus.email.proveFirst": true
|
|
345
368
|
}));
|
|
@@ -480,5 +503,5 @@ export {
|
|
|
480
503
|
instrumentPermissions
|
|
481
504
|
};
|
|
482
505
|
|
|
483
|
-
//# debugId=
|
|
506
|
+
//# debugId=C439719FEBEDF35064756E2164756E21
|
|
484
507
|
//# 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/
|
|
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 {
|
|
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
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}\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
13
|
],
|
|
13
|
-
"mappings": "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",
|
|
14
|
-
"debugId": "
|
|
14
|
+
"mappings": "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",
|
|
15
|
+
"debugId": "C439719FEBEDF35064756E2164756E21",
|
|
15
16
|
"names": []
|
|
16
17
|
}
|
package/docs/guide/tracing.md
CHANGED
|
@@ -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
|
|
@@ -351,6 +351,37 @@ POST /invitation/accept server
|
|
|
351
351
|
log janus.signIn user.id=0199… janus.signIn.proven=true
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
+
## A changed e-mail
|
|
355
|
+
|
|
356
|
+
`changeEmail.send` moves nobody by itself: it is counted by the mail throttle like
|
|
357
|
+
the other e-mail requests, so a refused one is a `janus.mail.throttled` with
|
|
358
|
+
`janus.mail.flow=changeEmail.send` and the `user.id`. `changeEmail.confirm`
|
|
359
|
+
moves the user to the new, proved address and writes `janus.email.changed`
|
|
360
|
+
with the `user.id` and `janus.email.proved=true`, and nothing else about the
|
|
361
|
+
move: neither the former nor the new address, and no `janus.email.verified`
|
|
362
|
+
besides, since the move is the proof. `update` writes the same event with
|
|
363
|
+
`janus.email.proved=false` when its patch names the `email` field, so a
|
|
364
|
+
move without proof is as visible as one with it. Telemetry sees only the
|
|
365
|
+
call's arguments and answer: the event is written for any patch that names
|
|
366
|
+
`email` — also one that turns out to keep the same address — and a user type
|
|
367
|
+
whose e-mail field is renamed is not seen on `update`, nor an `update` that
|
|
368
|
+
moved the address and then failed with `STORE_FAILED` while spending the
|
|
369
|
+
change links: the event is written for an answered call only. Whoever changes an
|
|
370
|
+
address takes over password resets, so this is the event to alert on:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
POST /email/confirm server
|
|
374
|
+
└─ janus.changeEmail.confirm janus.user.type=user user.id=0199…
|
|
375
|
+
log janus.email.changed user.id=0199…
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
A refusal (`TOKEN_STALE`, `LOGIN_TAKEN`, a `TOKEN_*` code) writes no event;
|
|
379
|
+
the span records the `janus.refusal`.
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// name = 'janus.email.changed' and janus.email.proved = false — who moved to another address without proof, and when
|
|
383
|
+
```
|
|
384
|
+
|
|
354
385
|
## A step-up
|
|
355
386
|
|
|
356
387
|
A step-up — a signed-in user proving again who they are before a
|
package/docs/roadmap.md
CHANGED
|
@@ -33,6 +33,13 @@ Nothing yet.
|
|
|
33
33
|
|
|
34
34
|
Newest first; the [CHANGELOG](../CHANGELOG.md) holds every release.
|
|
35
35
|
|
|
36
|
+
- **E-mail changes in the audit trail** — `changeEmail.confirm` writes
|
|
37
|
+
`janus.email.changed` with the `user.id` and `janus.email.proved: true`,
|
|
38
|
+
never an address (an `update` whose patch names `email` writes it with
|
|
39
|
+
`janus.email.proved: false`), and
|
|
40
|
+
`changeEmail.send` is counted by the mail throttle like the other e-mail
|
|
41
|
+
requests: `janus.mail.throttled` with `janus.mail.flow: changeEmail.send`.
|
|
42
|
+
Needs the `@nxgt/janus` that ships `changeEmail`.
|
|
36
43
|
- **An e-mail proved on the application's proof, traced, v0.11.0** —
|
|
37
44
|
`verifyEmail.proveFirst` is a span, `janus.<type>.verifyEmail.proveFirst`,
|
|
38
45
|
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.
|
|
3
|
+
"version": "0.12.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.
|
|
53
|
-
"@nxgt/telemetry": "0.2.
|
|
52
|
+
"@nxgt/janus": "^0.23.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.
|
|
58
|
+
"@nxgt/janus": "^0.23.0",
|
|
59
59
|
"@nxgt/telemetry": ">=0.2.1 <1",
|
|
60
60
|
"typescript": "^6.0.3 || ^7.0.0"
|
|
61
61
|
}
|