@nxgt/janus-telemetry 0.7.0 → 0.8.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 +12 -6
- package/dist/flows/events.d.ts.map +1 -1
- package/dist/flows/fields.d.ts +5 -0
- package/dist/flows/fields.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +4 -4
- package/docs/README.md +1 -1
- package/docs/guide/tracing.md +28 -1
- package/docs/roadmap.md +7 -2
- package/docs/troubleshooting.md +20 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,9 +6,10 @@ flow and per permission check, and the security events an audit reads — who
|
|
|
6
6
|
signed up, in and out, who was sent a sign-in code or link, why a sign-in was
|
|
7
7
|
refused, who was asked for a second factor and who enrolled, activated or
|
|
8
8
|
disabled one, which users were deleted
|
|
9
|
-
or deactivated, whose password changed,
|
|
10
|
-
login, an e-mail, a password, a session
|
|
11
|
-
challenge, a code, a TOTP secret
|
|
9
|
+
or deactivated, whose password changed, who signed in from a new device, and
|
|
10
|
+
who was granted what. **Never a login, an e-mail, a password, a session
|
|
11
|
+
token, a one-time token, a challenge, a code, a TOTP secret, a device token
|
|
12
|
+
or a session id.**
|
|
12
13
|
|
|
13
14
|
```ts
|
|
14
15
|
import { janus } from '@nxgt/janus';
|
|
@@ -33,7 +34,7 @@ bun add @nxgt/janus-telemetry @nxgt/janus @nxgt/telemetry
|
|
|
33
34
|
bun add -d typescript
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
Every peer is required: `@nxgt/janus` 0.
|
|
37
|
+
Every peer is required: `@nxgt/janus` 0.17 (the exact range is in
|
|
37
38
|
`peerDependencies`), `@nxgt/telemetry` (`>=0.2.1 <1`, any 0.x from 0.2.1) and
|
|
38
39
|
`typescript` (6). `@nxgt/janus` and `@nxgt/telemetry` are **peers**: one copy of `@nxgt/janus`, so
|
|
39
40
|
`instanceof JanusError` holds, and one of `@nxgt/telemetry`, so there is one
|
|
@@ -75,7 +76,7 @@ when the flow knows them — `janus.signOut` carries neither:
|
|
|
75
76
|
| `janus.signUp` | info | a user signed up |
|
|
76
77
|
| `janus.signInCode.sent` | info | `signInCode.request` issued a code, with the `user.id` it is for. A request that answered `null` writes nothing |
|
|
77
78
|
| `janus.magicLink.sent` | info | `magicLink.request` issued a sign-in link, with the `user.id` it is for — never its token. A request that answered `null` writes nothing |
|
|
78
|
-
| `janus.signIn` | info | a user signed in — by `signIn`; by `signInCode.confirm`, which adds `janus.signIn.code: true`; by `magicLink.confirm`, which adds `janus.signIn.magicLink: true`; by `secondFactor.confirm`, which adds `janus.signIn.secondFactor: true`; or by `secondFactor.recover`, which adds `janus.signIn.recoveryCode: true` and `janus.secondFactor.recoveryCodesLeft` — a count, never a code |
|
|
79
|
+
| `janus.signIn` | info | a user signed in — by `signIn`; by `signInCode.confirm`, which adds `janus.signIn.code: true`; by `magicLink.confirm`, which adds `janus.signIn.magicLink: true`; by `secondFactor.confirm`, which adds `janus.signIn.secondFactor: true`; or by `secondFactor.recover`, which adds `janus.signIn.recoveryCode: true` and `janus.secondFactor.recoveryCodesLeft` — a count, never a code. Any of them adds `janus.signIn.newDevice: true` when `@nxgt/janus` answered `newDevice: true` — a device the user had not signed in from — and never the device token |
|
|
79
80
|
| `janus.signIn.secondFactor` | info | the password, an e-mailed code or a sign-in link was right and a second factor's code was asked for: `signIn`, `signInCode.confirm` or `magicLink.confirm` answered a challenge. `janus.user.type` and the `user.id` the challenge answer carries — never the login typed |
|
|
80
81
|
| `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`, and every refusal of `secondFactor.recover` adds `janus.signIn.recoveryCode: true` |
|
|
81
82
|
| `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 |
|
|
@@ -125,13 +126,18 @@ when the flow knows them — `janus.signOut` carries neither:
|
|
|
125
126
|
`janus.signIn.magicLink: true`. Many `TOKEN_SPENT` on links never
|
|
126
127
|
confirmed twice by your users is the sign of a mail scanner opening them:
|
|
127
128
|
confirm from a `POST`, never from the link's `GET`.
|
|
129
|
+
- **`janus.signIn.newDevice` is only as good as the `device` given.** It is
|
|
130
|
+
written when the answer says `newDevice: true`, which needs
|
|
131
|
+
`janus({ devices })` and a sign-in given `{ device }`: a sign-in given no
|
|
132
|
+
device carries no mark, whatever browser it came from. Absent means *not
|
|
133
|
+
new, or not tracked* — never "known".
|
|
128
134
|
- **One copy of `@nxgt/janus`.** A refusal is told from a failure by
|
|
129
135
|
`instanceof JanusError`: with a second copy installed, every wrong password
|
|
130
136
|
fails its span.
|
|
131
137
|
|
|
132
138
|
## Documentation
|
|
133
139
|
|
|
134
|
-
- [Tracing Janus](docs/guide/tracing.md) — wiring it with a Hono app, reading the spans and events, the audit trail, a sign-in with a second factor
|
|
140
|
+
- [Tracing Janus](docs/guide/tracing.md) — wiring it with a Hono app, reading the spans and events, the audit trail, a sign-in with a second factor, an e-mailed code or from a new device
|
|
135
141
|
- [Troubleshooting](docs/troubleshooting.md) — what you see, why, and the fix
|
|
136
142
|
- [Roadmap](docs/roadmap.md) — what is next, and what is not planned
|
|
137
143
|
|
|
@@ -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;AAyEnC;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAuItD,CAAC"}
|
package/dist/flows/fields.d.ts
CHANGED
|
@@ -3,6 +3,11 @@ import { type Fields } from '../traced';
|
|
|
3
3
|
import type { Call } from './call';
|
|
4
4
|
/** The user an answer names — `{ user }` or the user itself — as fields. */
|
|
5
5
|
export declare function userFields(call: Call, value: unknown): Fields;
|
|
6
|
+
/**
|
|
7
|
+
* A session opened: the user, and `janus.signIn.newDevice` when the answer
|
|
8
|
+
* says the device was new to them — never the device token itself.
|
|
9
|
+
*/
|
|
10
|
+
export declare function signedInFields(call: Call, value: unknown): Fields;
|
|
6
11
|
/** Whose sign-in waits for a code: the `userId` the answer carries. */
|
|
7
12
|
export declare function secondFactorFields(call: Call, value: unknown): Fields;
|
|
8
13
|
/** The user a flow was called for: its first argument. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/flows/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,MAAM,EAA0B,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAS7D;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CASrE;AAED,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAK/C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAQrE;AAED,oDAAoD;AACpD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO3D;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CASpE;AAED,qFAAqF;AACrF,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOxD"}
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/flows/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,MAAM,EAA0B,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAS7D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAUjE;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CASrE;AAED,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAK/C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAQrE;AAED,oDAAoD;AACpD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO3D;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CASpE;AAED,qFAAqF;AACrF,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOxD"}
|
package/dist/index.js
CHANGED
|
@@ -96,6 +96,13 @@ function userFields(call, value) {
|
|
|
96
96
|
"user.id": idOf(user)
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
+
function signedInFields(call, value) {
|
|
100
|
+
const newDevice = typeof value === "object" && value !== null && "newDevice" in value && value.newDevice === true;
|
|
101
|
+
return {
|
|
102
|
+
...userFields(call, value),
|
|
103
|
+
...newDevice ? { "janus.signIn.newDevice": true } : {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
99
106
|
function secondFactorFields(call, value) {
|
|
100
107
|
const userId = typeof value === "object" && value !== null && "userId" in value ? value.userId : undefined;
|
|
101
108
|
return fieldsOf({
|
|
@@ -172,7 +179,10 @@ function emailedSignIn(mark) {
|
|
|
172
179
|
} else if (statusOf(outcome.value) === "secondFactor") {
|
|
173
180
|
log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
|
|
174
181
|
} else {
|
|
175
|
-
log.info(events.signedIn({
|
|
182
|
+
log.info(events.signedIn({
|
|
183
|
+
...signedInFields(call, outcome.value),
|
|
184
|
+
[mark]: true
|
|
185
|
+
}));
|
|
176
186
|
}
|
|
177
187
|
};
|
|
178
188
|
}
|
|
@@ -192,7 +202,7 @@ var WRITTEN = {
|
|
|
192
202
|
} else if (statusOf(outcome.value) === "secondFactor") {
|
|
193
203
|
log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
|
|
194
204
|
} else {
|
|
195
|
-
log.info(events.signedIn(
|
|
205
|
+
log.info(events.signedIn(signedInFields(call, outcome.value)));
|
|
196
206
|
}
|
|
197
207
|
},
|
|
198
208
|
"signInCode.request": (call, outcome) => {
|
|
@@ -225,7 +235,7 @@ var WRITTEN = {
|
|
|
225
235
|
"secondFactor.confirm": (call, outcome) => {
|
|
226
236
|
if (outcome.ok) {
|
|
227
237
|
log.info(events.signedIn({
|
|
228
|
-
...
|
|
238
|
+
...signedInFields(call, outcome.value),
|
|
229
239
|
"janus.signIn.secondFactor": true
|
|
230
240
|
}));
|
|
231
241
|
} else {
|
|
@@ -235,7 +245,7 @@ var WRITTEN = {
|
|
|
235
245
|
"secondFactor.recover": (call, outcome) => {
|
|
236
246
|
if (outcome.ok) {
|
|
237
247
|
log.info(events.signedIn({
|
|
238
|
-
...
|
|
248
|
+
...signedInFields(call, outcome.value),
|
|
239
249
|
"janus.signIn.recoveryCode": true,
|
|
240
250
|
"janus.secondFactor.recoveryCodesLeft": recoveryCodesLeft(outcome.value)
|
|
241
251
|
}));
|
|
@@ -424,5 +434,5 @@ export {
|
|
|
424
434
|
instrumentPermissions
|
|
425
435
|
};
|
|
426
436
|
|
|
427
|
-
//# debugId=
|
|
437
|
+
//# debugId=59C9B29FCBF61C5864756E2164756E21
|
|
428
438
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sources": ["../src/traced.ts", "../src/flows/events.ts", "../src/flows/fields.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 '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\trecoveryCodesLeft,\n\trefusalFields,\n\tsecondFactorFields,\n\tsessionUserFields,\n\tstatusOf,\n\tuserFields,\n\tviaOf,\n} from './fields';\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 by e-mail writes once confirmed — a code or a link —\n * marked, so an alert on refusals can tell one from the other and from a\n * password: a refusal, the second factor asked for, or a sign-in.\n */\nfunction emailedSignIn(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({
|
|
7
|
-
"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/** 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",
|
|
6
|
+
"import { createLogger, event } from '@nxgt/telemetry';\nimport type { Outcome } from '../traced';\nimport type { Call } from './call';\nimport {\n\targumentUser,\n\trecoveryCodesLeft,\n\trefusalFields,\n\tsecondFactorFields,\n\tsessionUserFields,\n\tsignedInFields,\n\tstatusOf,\n\tuserFields,\n\tviaOf,\n} from './fields';\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 by e-mail writes once confirmed — a code or a link —\n * marked, so an alert on refusals can tell one from the other and from a\n * password: a refusal, the second factor asked for, or a sign-in.\n */\nfunction emailedSignIn(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 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': (call, outcome) => {\n\t\t// Only when a code was issued: `null` is nobody, and says nothing.\n\t\tif (outcome.ok && outcome.value !== null) {\n\t\t\tlog.info(events.signInCodeSent(userFields(call, outcome.value)));\n\t\t}\n\t},\n\t'signInCode.confirm': emailedSignIn('janus.signIn.code'),\n\t'magicLink.request': (call, outcome) => {\n\t\t// Only when a link was issued: `null` is nobody, and says nothing.\n\t\tif (outcome.ok && outcome.value !== null) {\n\t\t\tlog.info(events.magicLinkSent(userFields(call, outcome.value)));\n\t\t}\n\t},\n\t'magicLink.confirm': emailedSignIn('janus.signIn.magicLink'),\n\t'stepUp.request': (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.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};\n",
|
|
7
|
+
"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",
|
|
8
8
|
"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(['signIn', 'signInCode.confirm', 'magicLink.confirm']);\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\treturn 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};\n}\n",
|
|
9
9
|
"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",
|
|
10
10
|
"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"
|
|
11
11
|
],
|
|
12
|
-
"mappings": "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",
|
|
13
|
-
"debugId": "
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "59C9B29FCBF61C5864756E2164756E21",
|
|
14
14
|
"names": []
|
|
15
15
|
}
|
package/docs/README.md
CHANGED
|
@@ -5,7 +5,7 @@ detail.
|
|
|
5
5
|
|
|
6
6
|
| Page | Read it when |
|
|
7
7
|
| --- | --- |
|
|
8
|
-
| [Tracing Janus](guide/tracing.md) | You are wiring it into an application, reading what it wrote, or following a sign-in with a second factor
|
|
8
|
+
| [Tracing Janus](guide/tracing.md) | You are wiring it into an application, reading what it wrote, or following a sign-in with a second factor, an e-mailed code or from a new device |
|
|
9
9
|
| [Troubleshooting](troubleshooting.md) | A span or an event is missing, or says something you did not expect |
|
|
10
10
|
| [Roadmap](roadmap.md) | You want to know what is coming, what shipped, and what is deliberately not planned |
|
|
11
11
|
|
package/docs/guide/tracing.md
CHANGED
|
@@ -310,12 +310,39 @@ session worth looking at — it may not be theirs:
|
|
|
310
310
|
// name = 'janus.stepUp.refused', grouped by user.id, more than 5 in an hour
|
|
311
311
|
```
|
|
312
312
|
|
|
313
|
+
## A new device
|
|
314
|
+
|
|
315
|
+
With `janus({ devices })`, a sign-in given the device token the client holds
|
|
316
|
+
answers whether the device is new to the user. When it is, the
|
|
317
|
+
`janus.signIn` event says so — on `signIn`, `signInCode.confirm`,
|
|
318
|
+
`magicLink.confirm`, `secondFactor.confirm` and `secondFactor.recover`
|
|
319
|
+
alike, beside their own marks:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
POST /sign-in server
|
|
323
|
+
└─ janus.signIn janus.user.type=user user.id=0199… janus.signIn.status=signedIn
|
|
324
|
+
log janus.signIn user.id=0199… janus.signIn.newDevice=true
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
A known device, or a sign-in given no `device`, writes no mark. The device
|
|
328
|
+
token itself is never written, in any span or event. With a second factor,
|
|
329
|
+
the mark is on the `janus.signIn` of the `confirm` or `recover` that opened
|
|
330
|
+
the session: the challenge knows no device.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
// Sign-ins from new devices, per user, in the last day:
|
|
334
|
+
// name = 'janus.signIn' AND janus.signIn.newDevice = true, grouped by user.id
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
A burst of them for one user is an account being tried from elsewhere —
|
|
338
|
+
worth an alert beside the notice `@nxgt/janus-mail` sends the user.
|
|
339
|
+
|
|
313
340
|
## What is never written
|
|
314
341
|
|
|
315
342
|
A login, an e-mail, a password, a session token, a one-time token, a
|
|
316
343
|
challenge — a second factor's or a sign-in code's — a code, a sign-in
|
|
317
344
|
link's token, a TOTP secret,
|
|
318
|
-
the `otpauth://` URI that holds it, a session id: nothing a log reader could
|
|
345
|
+
the `otpauth://` URI that holds it, a device token, a session id: nothing a log reader could
|
|
319
346
|
sign in with, or use to tell who holds an account. A refused sign-in by
|
|
320
347
|
an unknown login says `janus.refusal.reason: 'unknownLogin'`, not which login
|
|
321
348
|
was tried; only a refusal after the password was right, or against a
|
package/docs/roadmap.md
CHANGED
|
@@ -24,8 +24,8 @@ Nothing yet.
|
|
|
24
24
|
- **An OpenTelemetry SDK** — like `@nxgt/telemetry`, it speaks OTLP through its
|
|
25
25
|
exporters and depends on no `@opentelemetry/*` package.
|
|
26
26
|
- **Writing a login, an e-mail, a password, a session token, a one-time token,
|
|
27
|
-
a one-time code, a challenge or a session id** — in any
|
|
28
|
-
any setting.
|
|
27
|
+
a one-time code, a challenge, a device token or a session id** — in any
|
|
28
|
+
span or event, on any setting.
|
|
29
29
|
- **`moduleResolution: "nodenext"`** — like `@nxgt/janus`, the package imports
|
|
30
30
|
without extensions. Use `"moduleResolution": "bundler"`.
|
|
31
31
|
|
|
@@ -33,6 +33,11 @@ Nothing yet.
|
|
|
33
33
|
|
|
34
34
|
Newest first; the [CHANGELOG](../CHANGELOG.md) holds every release.
|
|
35
35
|
|
|
36
|
+
- **A sign-in from a new device, marked, v0.8.0** — `janus.signIn` carries
|
|
37
|
+
`janus.signIn.newDevice: true` when `@nxgt/janus` 0.17 answered
|
|
38
|
+
`newDevice: true`, on `signIn`, `signInCode.confirm`, `magicLink.confirm`,
|
|
39
|
+
`secondFactor.confirm` and `secondFactor.recover`. The device token is
|
|
40
|
+
never written.
|
|
36
41
|
- **Sign-in links in the audit trail, v0.7.0** — `janus.magicLink.sent`
|
|
37
42
|
when `magicLink.request` issued a link, never its token nor the address;
|
|
38
43
|
a sign-in by link is `janus.signIn` with `janus.signIn.magicLink: true`,
|
package/docs/troubleshooting.md
CHANGED
|
@@ -14,6 +14,7 @@ would be without it.
|
|
|
14
14
|
- [A `janus.signInCode.request` span with no `janus.signInCode.sent` event](#a-janussignincoderequest-span-with-no-janussignincodesent-event)
|
|
15
15
|
- [`janus.signIn.refused` with `TOKEN_SPENT` and `janus.signIn.magicLink`, for users who clicked once](#janussigninrefused-with-token_spent-and-janussigninmagiclink-for-users-who-clicked-once)
|
|
16
16
|
- [Every wrong password fails its span](#every-wrong-password-fails-its-span)
|
|
17
|
+
- [`janus.signIn.newDevice` never appears](#janussigninnewdevice-never-appears)
|
|
17
18
|
|
|
18
19
|
### No `janus.*` span or event at all
|
|
19
20
|
|
|
@@ -132,3 +133,22 @@ failure.
|
|
|
132
133
|
|
|
133
134
|
**Fix:** keep one copy: `bun pm ls | grep @nxgt/janus` should print one
|
|
134
135
|
version. Align the versions, or dedupe the lockfile.
|
|
136
|
+
|
|
137
|
+
### `janus.signIn.newDevice` never appears
|
|
138
|
+
|
|
139
|
+
**When:** users sign in from new browsers, and no `janus.signIn` log carries
|
|
140
|
+
`janus.signIn.newDevice`.
|
|
141
|
+
|
|
142
|
+
**Why:** the mark copies the answer's `newDevice`, and janus answers `false`
|
|
143
|
+
whenever the device is not tracked: `janus()` has no `devices`, the sign-in
|
|
144
|
+
was given no `{ device }` — absent is "not tracked", `null` is "a device
|
|
145
|
+
holding no token" — or, with a second factor, `{ device }` was given to
|
|
146
|
+
`signIn` but not again to `secondFactor.confirm` or `recover`, which open
|
|
147
|
+
the session.
|
|
148
|
+
|
|
149
|
+
**Fix:** wire `devices`, and give every call that opens a session the
|
|
150
|
+
device, `null` when the client holds no token.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
await auth.secondFactor.confirm(challenge, code, { device: cookie ?? null });
|
|
154
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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.
|
|
52
|
+
"@nxgt/janus": "^0.17.0",
|
|
53
53
|
"@nxgt/telemetry": "0.2.1",
|
|
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.17.0",
|
|
59
59
|
"@nxgt/telemetry": ">=0.2.1 <1",
|
|
60
60
|
"typescript": "^6.0.3"
|
|
61
61
|
}
|