@nxgt/janus-telemetry 0.3.0 → 0.3.1
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 +1 -1
- package/dist/flows.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +3 -3
- package/docs/guide/tracing.md +6 -6
- package/docs/roadmap.md +3 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ when the flow knows them — `janus.signOut` carries neither:
|
|
|
74
74
|
| `janus.signUp` | info | a user signed up |
|
|
75
75
|
| `janus.signInCode.sent` | info | `signInCode.request` issued a code, with the `user.id` it is for. A request that answered `null` writes nothing |
|
|
76
76
|
| `janus.signIn` | info | a user signed in — by `signIn`; by `signInCode.confirm`, which adds `janus.signIn.code: true`; or by `secondFactor.confirm`, which adds `janus.signIn.secondFactor: true` |
|
|
77
|
-
| `janus.signIn.secondFactor` | info | the password, or an e-mailed code, was right and a second factor's code was asked for: `signIn` or `signInCode.confirm` answered a challenge. `janus.user.type`
|
|
77
|
+
| `janus.signIn.secondFactor` | info | the password, or an e-mailed code, was right and a second factor's code was asked for: `signIn` or `signInCode.confirm` answered a challenge. `janus.user.type` and the `user.id` the challenge answer carries — never the login typed |
|
|
78
78
|
| `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` |
|
|
79
79
|
| `janus.secondFactor.enrolled`, `janus.secondFactor.activated`, `janus.secondFactor.disabled` | info | `enroll`, `activate` and `disable`, with the `user.id` they were called for |
|
|
80
80
|
| `janus.signOut` | info | a session was signed out |
|
package/dist/flows.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../src/flows.ts"],"names":[],"mappings":"AAkCA,kFAAkF;AAClF,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../src/flows.ts"],"names":[],"mappings":"AAkCA,kFAAkF;AAClF,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAgMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAQ/D"}
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ var WRITTEN = {
|
|
|
117
117
|
if (!outcome.ok) {
|
|
118
118
|
log.warn(events.signInRefused(refusalFields(call, outcome.refusal)));
|
|
119
119
|
} else if (statusOf(outcome.value) === "secondFactor") {
|
|
120
|
-
log.info(events.secondFactorAsked(
|
|
120
|
+
log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
|
|
121
121
|
} else {
|
|
122
122
|
log.info(events.signedIn(userFields(call, outcome.value)));
|
|
123
123
|
}
|
|
@@ -134,7 +134,7 @@ var WRITTEN = {
|
|
|
134
134
|
"janus.signIn.code": true
|
|
135
135
|
}));
|
|
136
136
|
} else if (statusOf(outcome.value) === "secondFactor") {
|
|
137
|
-
log.info(events.secondFactorAsked(
|
|
137
|
+
log.info(events.secondFactorAsked(secondFactorFields(call, outcome.value)));
|
|
138
138
|
} else {
|
|
139
139
|
log.info(events.signedIn({
|
|
140
140
|
...userFields(call, outcome.value),
|
|
@@ -207,6 +207,13 @@ function userFields(call, value) {
|
|
|
207
207
|
"user.id": idOf(user)
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
|
+
function secondFactorFields(call, value) {
|
|
211
|
+
const userId = typeof value === "object" && value !== null && "userId" in value ? value.userId : undefined;
|
|
212
|
+
return fieldsOf({
|
|
213
|
+
"janus.user.type": call.userType,
|
|
214
|
+
"user.id": typeof userId === "string" ? userId : undefined
|
|
215
|
+
});
|
|
216
|
+
}
|
|
210
217
|
function argumentUser(call) {
|
|
211
218
|
return fieldsOf({
|
|
212
219
|
"janus.user.type": call.userType,
|
|
@@ -345,5 +352,5 @@ export {
|
|
|
345
352
|
instrumentPermissions
|
|
346
353
|
};
|
|
347
354
|
|
|
348
|
-
//# debugId=
|
|
355
|
+
//# debugId=07AF589B0E112A6864756E2164756E21
|
|
349
356
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/flows.ts", "../src/traced.ts", "../src/permissions.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { JanusError } from '@nxgt/janus';\nimport { createLogger, event, type SpanScope } from '@nxgt/telemetry';\nimport {\n\ttype Fields,\n\tfieldsOf,\n\tidOf,\n\ttype Outcome,\n\ttraced,\n\ttypeOf,\n} from './traced';\n\nconst log = createLogger('@nxgt/janus');\n\n/** The events a security review reads: who signed in, out, up, and what changed. */\nconst events = {\n\tsignedUp: event('janus.signUp'),\n\tsignedIn: event('janus.signIn'),\n\tsignInRefused: event('janus.signIn.refused'),\n\tsecondFactorAsked: event('janus.signIn.secondFactor'),\n\tsignInCodeSent: event('janus.signInCode.sent'),\n\tsecondFactorEnrolled: event('janus.secondFactor.enrolled'),\n\tsecondFactorActivated: event('janus.secondFactor.activated'),\n\tsecondFactorDisabled: event('janus.secondFactor.disabled'),\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/** 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/** One call of a flow, as the events read it. */\ninterface Call {\n\t/** `signIn`, `verifyEmail.confirm`: the flow without the user type. */\n\treadonly flow: string;\n\treadonly userType: string | undefined;\n\treadonly args: readonly unknown[];\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 */\nconst 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) {\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(\n\t\t\t\t\tfieldsOf({ 'janus.user.type': call.userType }),\n\t\t\t\t),\n\t\t\t);\n\t\t} else {\n\t\t\tlog.info(events.signedIn(userFields(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': (call, outcome) => {\n\t\tif (!outcome.ok) {\n\t\t\t// Marked, so an alert on burnt challenges can tell the two codes apart.\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.code': 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(\n\t\t\t\t\tfieldsOf({ 'janus.user.type': call.userType }),\n\t\t\t\t),\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...userFields(call, outcome.value),\n\t\t\t\t\t'janus.signIn.code': true,\n\t\t\t\t}),\n\t\t\t);\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...userFields(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.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\n/** The user an answer names — `{ user }` or the user itself — as fields. */\nfunction 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/** The user a flow was called for: its first argument. */\nfunction 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 */\nfunction 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. */\nfunction 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/** 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\tconst fields = 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 =\n\t\t\tcall.flow === 'signIn' || call.flow === 'signInCode.confirm'\n\t\t\t\t? statusOf(value)\n\t\t\t\t: undefined;\n\t\tif (status !== undefined) scope.attribute('janus.signIn.status', status);\n\t}\n\tWRITTEN[call.flow]?.(call, outcome);\n}\n\n/** `cookie` answers synchronously: it is the one part of `janus()` not traced. */\nconst UNTRACED = new Set(['cookie']);\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,\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\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 */\nfunction 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",
|
|
5
|
+
"import type { JanusError } from '@nxgt/janus';\nimport { createLogger, event, type SpanScope } from '@nxgt/telemetry';\nimport {\n\ttype Fields,\n\tfieldsOf,\n\tidOf,\n\ttype Outcome,\n\ttraced,\n\ttypeOf,\n} from './traced';\n\nconst log = createLogger('@nxgt/janus');\n\n/** The events a security review reads: who signed in, out, up, and what changed. */\nconst events = {\n\tsignedUp: event('janus.signUp'),\n\tsignedIn: event('janus.signIn'),\n\tsignInRefused: event('janus.signIn.refused'),\n\tsecondFactorAsked: event('janus.signIn.secondFactor'),\n\tsignInCodeSent: event('janus.signInCode.sent'),\n\tsecondFactorEnrolled: event('janus.secondFactor.enrolled'),\n\tsecondFactorActivated: event('janus.secondFactor.activated'),\n\tsecondFactorDisabled: event('janus.secondFactor.disabled'),\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/** 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/** One call of a flow, as the events read it. */\ninterface Call {\n\t/** `signIn`, `verifyEmail.confirm`: the flow without the user type. */\n\treadonly flow: string;\n\treadonly userType: string | undefined;\n\treadonly args: readonly unknown[];\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 */\nconst 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) {\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(userFields(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': (call, outcome) => {\n\t\tif (!outcome.ok) {\n\t\t\t// Marked, so an alert on burnt challenges can tell the two codes apart.\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.code': 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...userFields(call, outcome.value),\n\t\t\t\t\t'janus.signIn.code': true,\n\t\t\t\t}),\n\t\t\t);\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...userFields(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.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\n/** The user an answer names — `{ user }` or the user itself — as fields. */\nfunction 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. */\nfunction 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. */\nfunction 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 */\nfunction 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. */\nfunction 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/** 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\tconst fields = 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 =\n\t\t\tcall.flow === 'signIn' || call.flow === 'signInCode.confirm'\n\t\t\t\t? statusOf(value)\n\t\t\t\t: undefined;\n\t\tif (status !== undefined) scope.attribute('janus.signIn.status', status);\n\t}\n\tWRITTEN[call.flow]?.(call, outcome);\n}\n\n/** `cookie` answers synchronously: it is the one part of `janus()` not traced. */\nconst UNTRACED = new Set(['cookie']);\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,\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\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 */\nfunction 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",
|
|
6
6
|
"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 '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",
|
|
7
7
|
"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"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "07AF589B0E112A6864756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
package/docs/guide/tracing.md
CHANGED
|
@@ -94,17 +94,17 @@ two spans and two events. The `signIn` span says which answer it gave, in
|
|
|
94
94
|
```
|
|
95
95
|
POST /sign-in server
|
|
96
96
|
└─ janus.signIn janus.user.type=user janus.signIn.status=secondFactor
|
|
97
|
-
log janus.signIn.secondFactor janus.user.type=user
|
|
97
|
+
log janus.signIn.secondFactor janus.user.type=user user.id=0199…
|
|
98
98
|
|
|
99
99
|
POST /sign-in/code server
|
|
100
100
|
└─ janus.secondFactor.confirm janus.user.type=user user.id=0199…
|
|
101
101
|
log janus.signIn user.id=0199… janus.signIn.secondFactor=true
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
The first event
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
The first event names the user by id — the `userId` the challenge answer
|
|
105
|
+
carries — never by the login that was typed. The `janus.signIn` written by
|
|
106
|
+
`confirm` names the user too, and `janus.signIn.secondFactor: true` tells it
|
|
107
|
+
from a sign-in by password alone.
|
|
108
108
|
|
|
109
109
|
A wrong code is a refusal, so the span stays `ok`, and a warning names the
|
|
110
110
|
user and what the challenge has left:
|
|
@@ -162,7 +162,7 @@ is `secondFactor`, the event is `janus.signIn.secondFactor`, and the
|
|
|
162
162
|
```
|
|
163
163
|
POST /sign-in/email/code server
|
|
164
164
|
└─ janus.signInCode.confirm janus.user.type=user janus.signIn.status=secondFactor
|
|
165
|
-
log janus.signIn.secondFactor janus.user.type=user
|
|
165
|
+
log janus.signIn.secondFactor janus.user.type=user user.id=0199…
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
A refused code is the same warning as a second factor's — the attribute
|
package/docs/roadmap.md
CHANGED
|
@@ -33,6 +33,9 @@ Nothing yet.
|
|
|
33
33
|
|
|
34
34
|
Newest first; the [CHANGELOG](../CHANGELOG.md) holds every release.
|
|
35
35
|
|
|
36
|
+
- **Whose sign-in waits for a second factor, v0.3.1** —
|
|
37
|
+
`janus.signIn.secondFactor` carries the `user.id` of the user asked for a
|
|
38
|
+
code, from `signIn` and from `signInCode.confirm` alike.
|
|
36
39
|
- **Sign-in codes in the audit trail, v0.3.0** — a `janus.signInCode.sent`
|
|
37
40
|
event for every code issued, never for an address nobody holds; a sign-in
|
|
38
41
|
by code is a `janus.signIn` with `janus.signIn.code: true`, and the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-telemetry",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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.7.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.7.0",
|
|
59
59
|
"@nxgt/telemetry": "^0.2.1",
|
|
60
60
|
"typescript": "^6.0.3"
|
|
61
61
|
}
|