@data-fair/lib-node 2.16.0 → 2.17.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 ADDED
@@ -0,0 +1,137 @@
1
+ # @data-fair/lib-node
2
+
3
+ Server-side Node.js utilities shared by the services of the data-fair stack (data-fair, processings, events, catalogs…): HTTP clients protected against SSRF, Prometheus observability, MongoDB helpers, upgrade scripts, websocket emitter and client.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm i @data-fair/lib-node
9
+ ```
10
+
11
+ Peer dependencies, all declared optional: install the ones required by the modules you import.
12
+
13
+ | Peer dependency | Needed by |
14
+ | --- | --- |
15
+ | `prom-client@15` | `observer.js`, `http-agents.js` and everything built on them (`axios.js`, `events-queue.js`…) |
16
+ | `mongodb@6` | `mongo.js`, `locks.js`, `upgrade-scripts.js` |
17
+ | `ws@8` | `ws-client.js` |
18
+ | `tough-cookie@5` | `axios-with-cookies.js`, `axios-auth.js` |
19
+
20
+ There is no root entry point: import each module by its path.
21
+
22
+ ## Modules
23
+
24
+ | Import path | Description |
25
+ | --- | --- |
26
+ | `@data-fair/lib-node/axios.js` | Axios instances with shorter error stack traces and a data-fair `User-Agent`: `axiosInstance` (default export, public), `privateAxiosInstance`, `axiosBuilder()`. |
27
+ | `@data-fair/lib-node/http-agents.js` | Keepalive HTTP(S) agents with cached DNS lookups and Prometheus metrics: `httpAgent` / `httpsAgent` (public, SSRF protected), `privateHttpAgent` / `privateHttpsAgent`. |
28
+ | `@data-fair/lib-node/ssrf.js` | SSRF checks for clients that don't use the agents above: `resolvePublicAddress()`, `publicLookup`, `SsrfError`. |
29
+ | `@data-fair/lib-node/axios-with-cookies.js` | Axios instance with a `tough-cookie` cookie jar. |
30
+ | `@data-fair/lib-node/axios-auth.js` | Axios instance logged into simple-directory with email/password. Integration tests only. |
31
+ | `@data-fair/lib-node/observer.js` | Mini web server exposing Prometheus metrics and CPU/heap profiles, plus the shared `internalError()` counter. |
32
+ | `@data-fair/lib-node/mongo.js` | `Mongo` connection wrapper (default export: a singleton) with declarative named index management (`configure()`). |
33
+ | `@data-fair/lib-node/locks.js` | Distributed locks stored in a MongoDB `locks` collection with a TTL (default export: a singleton). |
34
+ | `@data-fair/lib-node/upgrade-scripts.js` | Runner for the database migration scripts of a service. |
35
+ | `@data-fair/lib-node/events-queue.js` | Queue of events and notifications pushed to the events service. |
36
+ | `@data-fair/lib-node/session.js` | `SessionHandler`: reads and verifies simple-directory session cookies (JWKS). Used by `@data-fair/lib-express/session.js` and Nuxt server code. |
37
+ | `@data-fair/lib-node/ws-emitter.js` | Publishes websocket messages through a capped MongoDB collection, read by `@data-fair/lib-express/ws-server.js`. |
38
+ | `@data-fair/lib-node/ws-client.js` | Websocket client with auto-reconnect and channel subscriptions (`WsClient`, `DataFairWsClient`). |
39
+ | `@data-fair/lib-node/fs.js` | `ensureDir()`, `emptyDir()`, `exists()`. |
40
+ | `@data-fair/lib-node/validation.js` | Re-export of `@data-fair/lib-validation`. |
41
+ | `@data-fair/lib-node/test-spies.js` | Test helpers: turns `// @test:spy(...)` comments into events that tests can `waitFor()`. |
42
+
43
+ For sessions see the [`data-fair-session` skill](https://github.com/data-fair/lib/tree/main/skills/data-fair-session), for websockets the [`data-fair-ws` skill](https://github.com/data-fair/lib/tree/main/skills/data-fair-ws).
44
+
45
+ ## Outgoing HTTP requests and SSRF
46
+
47
+ The default axios instance and the public agents refuse to connect to non public addresses (loopback, private ranges, link-local including the cloud metadata service, etc.). Use them for every URL that a user can influence.
48
+
49
+ `privateAxiosInstance` and the private agents have no such restriction. Use them only for URLs that come from the configuration of the service (other services of the same infrastructure).
50
+
51
+ ```ts
52
+ import axios, { privateAxiosInstance } from '@data-fair/lib-node/axios.js'
53
+
54
+ await axios.get(userProvidedUrl)
55
+ await privateAxiosInstance.get(config.privateDirectoryUrl + '/api/...')
56
+ ```
57
+
58
+ - `SSRF_PUBLIC_IPS`: comma separated IPs / CIDR ranges to consider public, for example an intranet data source of a self-hosted instance, or `127.0.0.1,::1` in a development environment.
59
+ - `SSRF_PRIVATE_IPS`: comma separated IPs / CIDR ranges to consider private even if they are public addresses, for example the database servers.
60
+
61
+ If `HTTP_PROXY`, `HTTPS_PROXY` or `ALL_PROXY` (or their lowercase forms) is defined, the protection of the agents is disabled (they connect to the proxy, not to the target) and a warning is logged at startup.
62
+
63
+ For clients that don't use these agents (ftp, sftp, raw sockets, SDKs…), `@data-fair/lib-node/ssrf.js` exports `resolvePublicAddress(host)` (checks every address of the host and returns the one to connect to) and `publicLookup` (a `dns.lookup` compatible function for any `lookup` option). They apply the same rules and are not disabled by a proxy env variable. `publicLookup` is not called for IP literals: check those with `resolvePublicAddress`.
64
+
65
+ See the [`data-fair-security` skill](https://github.com/data-fair/lib/tree/main/skills/data-fair-security) for the stack-wide rules.
66
+
67
+ ## Observer
68
+
69
+ A very light web server for observability, on port 9090 by default:
70
+
71
+ - `GET /metrics`: Prometheus metrics of this process,
72
+ - `GET /service-metrics` (or `/global-metrics`): metrics registered in `servicePromRegistry`,
73
+ - `GET /cpu-profile`, `/heap-profile`, `/heap-profile-all` (optional `?duration=` in ms, default 2000): sampled profiles,
74
+ - `GET /heap-snapshot`: full retained heap snapshot (blocks the event loop while V8 walks the heap).
75
+
76
+ ```ts
77
+ import { startObserver, stopObserver, internalError } from '@data-fair/lib-node/observer.js'
78
+
79
+ await startObserver() // or startObserver(port)
80
+ // ...
81
+ await stopObserver()
82
+ ```
83
+
84
+ `internalError(errorCode, error, ...optionalParams)` increments the shared `df_internal_error` counter and logs the error. Use it for anomalies that should trigger alerts, not for client errors:
85
+
86
+ ```ts
87
+ internalError('http', 'failure while serving http request', err)
88
+ ```
89
+
90
+ Process metrics are plain `prom-client` metrics. A service-level metric (its value depends on a shared state, like the database, not on the activity of the current process) is registered in `servicePromRegistry`:
91
+
92
+ ```ts
93
+ import { Gauge } from 'prom-client'
94
+ import { servicePromRegistry } from '@data-fair/lib-node/observer.js'
95
+
96
+ new Gauge({
97
+ name: 'df_my_gauge',
98
+ help: '...',
99
+ registers: [servicePromRegistry],
100
+ async collect () {
101
+ this.set(await db.collection('my-collection').estimatedDocumentCount())
102
+ }
103
+ })
104
+ ```
105
+
106
+ ## Upgrade scripts
107
+
108
+ Scripts live in `upgrade/<last-released-version>/` directories of the service. The folder name must be the version of the **last release** of the service at the time the script is written, never an anticipated future version.
109
+
110
+ At startup the runner reads the version stored in the `services` Mongo collection, runs every script whose folder version is `>=` that stored version, then stores the current `package.json` version. So a script runs once on the production upgrade and re-runs on every staging deploy until the next release ships.
111
+
112
+ **Scripts must be idempotent**: each script should run once, but this cannot be strictly ensured (concurrent pods, crashes, manual re-runs, misnamed folders), so running it again must be a no-op, not a failure.
113
+
114
+ A script is a module whose default export follows the `UpgradeScript` interface:
115
+
116
+ ```ts
117
+ import type { UpgradeScript } from '@data-fair/lib-node/upgrade-scripts.js'
118
+
119
+ export default {
120
+ description: 'Add a default status to items',
121
+ async exec (db, debug) {
122
+ await db.collection('items').updateMany({ status: { $exists: false } }, { $set: { status: 'active' } })
123
+ }
124
+ } as UpgradeScript
125
+ ```
126
+
127
+ Run them during service initialization (the runner takes the `upgrade` lock, so only one process runs them):
128
+
129
+ ```ts
130
+ import upgradeScripts from '@data-fair/lib-node/upgrade-scripts.js'
131
+ import locks from '@data-fair/lib-node/locks.js'
132
+
133
+ await locks.start(db)
134
+ await upgradeScripts(db, locks) // optional: basePath = './', isFresh callback
135
+ ```
136
+
137
+ The [`upgrade-scripts` skill](https://github.com/data-fair/lib/tree/main/skills/upgrade-scripts) covers the folder-naming rule, idempotency patterns and fresh installs in detail.
package/events-queue.d.ts CHANGED
@@ -2,8 +2,8 @@ import { type Event } from '@data-fair/lib-common-types/event/index.js';
2
2
  import { type Notification } from '@data-fair/lib-common-types/notification/index.js';
3
3
  import { SessionState } from '@data-fair/lib-common-types/session/index.js';
4
4
  export type EventsQueueOptions = {
5
- eventsUrl: string;
6
- eventsSecret: string;
5
+ eventsUrl?: string | null;
6
+ eventsSecret?: string | null;
7
7
  inactive?: boolean;
8
8
  };
9
9
  export type PushEvent = Omit<Event, 'date'>;
package/events-queue.js CHANGED
@@ -17,8 +17,15 @@ export class EventsQueue {
17
17
  start = async (options) => {
18
18
  if (this._options)
19
19
  return; // already initialized
20
- this._options = options;
21
- this.loop();
20
+ // events is an optional service: without its url it is not deployed and events are dropped
21
+ let inactive = !!options.inactive || !options.eventsUrl;
22
+ if (!inactive && !options.eventsSecret) {
23
+ internalError('events-queue', 'the events service url is set but its secret is missing, events are dropped');
24
+ inactive = true;
25
+ }
26
+ this._options = { ...options, inactive };
27
+ if (!inactive)
28
+ this.loop();
22
29
  };
23
30
  stop = async () => {
24
31
  this.stopped = true;
@@ -1 +1 @@
1
- {"version":3,"file":"events-queue.js","sourceRoot":"","sources":["events-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Event } from '@data-fair/lib-common-types/event/index.js'\nimport { type Notification } from '@data-fair/lib-common-types/notification/index.js'\nimport { internalError } from '@data-fair/lib-node/observer.js'\n// the events service is part of our own infrastructure (configuration, not user input)\nimport { privateAxiosInstance as axios } from './axios.js'\nimport Debug from 'debug'\nimport { SessionState } from '@data-fair/lib-common-types/session/index.js'\n\nconst debug = Debug('events-queue')\n\nexport type EventsQueueOptions = {\n eventsUrl: string,\n eventsSecret: string,\n inactive?: boolean\n}\n\nexport type PushEvent = Omit<Event, 'date'>\nexport type PushNotification = Omit<Notification, 'date'>\n\nexport class EventsQueue {\n _options: EventsQueueOptions | undefined\n eventsQueue: Event[] = []\n notificationsQueue: Notification[] = []\n stopped = false\n currentDrainPromise: Promise<void> | undefined\n\n options () {\n if (!this._options) throw new Error('events queue was not initialized')\n return this._options\n }\n\n start = async (options: EventsQueueOptions) => {\n if (this._options) return // already initialized\n this._options = options\n this.loop()\n }\n\n stop = async () => {\n this.stopped = true\n if (this.currentDrainPromise) await this.currentDrainPromise\n await this.drain()\n }\n\n async loop () {\n while (!this.stopped) {\n await new Promise(resolve => setTimeout(resolve, 3000))\n this.currentDrainPromise = this.drain()\n await this.currentDrainPromise\n this.currentDrainPromise = undefined\n }\n }\n\n async drain () {\n if (this.eventsQueue.length) {\n const events = this.eventsQueue\n this.eventsQueue = []\n debug('drain events', events.length)\n try {\n await axios.post(this.options().eventsUrl + '/api/events', events, { headers: { 'x-secret-key': this.options().eventsSecret } })\n } catch (err: any) {\n internalError('events-queue-push-event', err)\n // retry later\n if (err.status >= 500 && events.length < 1000) this.eventsQueue = events.concat(this.eventsQueue)\n }\n }\n if (this.notificationsQueue.length) {\n const notifications = this.notificationsQueue\n this.notificationsQueue = []\n debug('drain notifications', notifications.length)\n for (const notification of notifications) {\n try {\n await axios.post(this.options().eventsUrl + '/api/notifications', notification, { headers: { 'x-secret-key': this.options().eventsSecret } })\n } catch (err: any) {\n internalError('events-queue-push-notif', err)\n // retry later\n if (err.status >= 500 && (notifications.length + this.notificationsQueue.length) < 1000) {\n this.notificationsQueue.unshift(notification)\n }\n }\n }\n }\n }\n\n pushEvent (event: PushEvent, sessionState?: SessionState) {\n const options = this.options()\n if (options.inactive) return\n if (this.stopped) throw new Error('events queue has been stopped');\n (event as Event).date = new Date().toISOString()\n debug('pushEvent', event)\n if (!event.originator && sessionState?.user && sessionState.account) {\n if (sessionState.user.asAdmin) {\n event.originator = {\n user: { ...sessionState.user.asAdmin, admin: true }\n }\n } else if (sessionState.user.adminMode && (sessionState.account.type !== event.sender?.type || sessionState.account.id !== event.sender?.id)) {\n event.originator = {\n user: { id: sessionState.user.id, name: sessionState.user.name, admin: true }\n }\n } else {\n event.originator = {\n user: { id: sessionState.user.id, name: sessionState.user.name }\n }\n if (sessionState.organization) {\n event.originator.organization = {\n id: sessionState.organization.id,\n name: sessionState.organization.name,\n department: sessionState.organization.department,\n departmentName: sessionState.organization.departmentName\n }\n }\n }\n }\n this.eventsQueue.push(event as Event)\n }\n\n pushNotification (notification: PushNotification) {\n const options = this.options()\n if (options.inactive) return\n if (this.stopped) throw new Error('notifications queue has been stopped');\n (notification as Notification).date = new Date().toISOString()\n debug('pushNotification', notification)\n this.notificationsQueue.push(notification as Notification)\n }\n}\n\nconst eventsQueue = new EventsQueue()\n\nexport default eventsQueue\n"]}
1
+ {"version":3,"file":"events-queue.js","sourceRoot":"","sources":["events-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Event } from '@data-fair/lib-common-types/event/index.js'\nimport { type Notification } from '@data-fair/lib-common-types/notification/index.js'\nimport { internalError } from '@data-fair/lib-node/observer.js'\n// the events service is part of our own infrastructure (configuration, not user input)\nimport { privateAxiosInstance as axios } from './axios.js'\nimport Debug from 'debug'\nimport { SessionState } from '@data-fair/lib-common-types/session/index.js'\n\nconst debug = Debug('events-queue')\n\nexport type EventsQueueOptions = {\n eventsUrl?: string | null,\n eventsSecret?: string | null,\n inactive?: boolean\n}\n\nexport type PushEvent = Omit<Event, 'date'>\nexport type PushNotification = Omit<Notification, 'date'>\n\nexport class EventsQueue {\n _options: EventsQueueOptions | undefined\n eventsQueue: Event[] = []\n notificationsQueue: Notification[] = []\n stopped = false\n currentDrainPromise: Promise<void> | undefined\n\n options () {\n if (!this._options) throw new Error('events queue was not initialized')\n return this._options\n }\n\n start = async (options: EventsQueueOptions) => {\n if (this._options) return // already initialized\n // events is an optional service: without its url it is not deployed and events are dropped\n let inactive = !!options.inactive || !options.eventsUrl\n if (!inactive && !options.eventsSecret) {\n internalError('events-queue', 'the events service url is set but its secret is missing, events are dropped')\n inactive = true\n }\n this._options = { ...options, inactive }\n if (!inactive) this.loop()\n }\n\n stop = async () => {\n this.stopped = true\n if (this.currentDrainPromise) await this.currentDrainPromise\n await this.drain()\n }\n\n async loop () {\n while (!this.stopped) {\n await new Promise(resolve => setTimeout(resolve, 3000))\n this.currentDrainPromise = this.drain()\n await this.currentDrainPromise\n this.currentDrainPromise = undefined\n }\n }\n\n async drain () {\n if (this.eventsQueue.length) {\n const events = this.eventsQueue\n this.eventsQueue = []\n debug('drain events', events.length)\n try {\n await axios.post(this.options().eventsUrl + '/api/events', events, { headers: { 'x-secret-key': this.options().eventsSecret } })\n } catch (err: any) {\n internalError('events-queue-push-event', err)\n // retry later\n if (err.status >= 500 && events.length < 1000) this.eventsQueue = events.concat(this.eventsQueue)\n }\n }\n if (this.notificationsQueue.length) {\n const notifications = this.notificationsQueue\n this.notificationsQueue = []\n debug('drain notifications', notifications.length)\n for (const notification of notifications) {\n try {\n await axios.post(this.options().eventsUrl + '/api/notifications', notification, { headers: { 'x-secret-key': this.options().eventsSecret } })\n } catch (err: any) {\n internalError('events-queue-push-notif', err)\n // retry later\n if (err.status >= 500 && (notifications.length + this.notificationsQueue.length) < 1000) {\n this.notificationsQueue.unshift(notification)\n }\n }\n }\n }\n }\n\n pushEvent (event: PushEvent, sessionState?: SessionState) {\n const options = this.options()\n if (options.inactive) return\n if (this.stopped) throw new Error('events queue has been stopped');\n (event as Event).date = new Date().toISOString()\n debug('pushEvent', event)\n if (!event.originator && sessionState?.user && sessionState.account) {\n if (sessionState.user.asAdmin) {\n event.originator = {\n user: { ...sessionState.user.asAdmin, admin: true }\n }\n } else if (sessionState.user.adminMode && (sessionState.account.type !== event.sender?.type || sessionState.account.id !== event.sender?.id)) {\n event.originator = {\n user: { id: sessionState.user.id, name: sessionState.user.name, admin: true }\n }\n } else {\n event.originator = {\n user: { id: sessionState.user.id, name: sessionState.user.name }\n }\n if (sessionState.organization) {\n event.originator.organization = {\n id: sessionState.organization.id,\n name: sessionState.organization.name,\n department: sessionState.organization.department,\n departmentName: sessionState.organization.departmentName\n }\n }\n }\n }\n this.eventsQueue.push(event as Event)\n }\n\n pushNotification (notification: PushNotification) {\n const options = this.options()\n if (options.inactive) return\n if (this.stopped) throw new Error('notifications queue has been stopped');\n (notification as Notification).date = new Date().toISOString()\n debug('pushNotification', notification)\n this.notificationsQueue.push(notification as Notification)\n }\n}\n\nconst eventsQueue = new EventsQueue()\n\nexport default eventsQueue\n"]}
@@ -31,5 +31,27 @@ describe('events queue', () => {
31
31
  assert.equal(received[0].url, '/api/events');
32
32
  assert.equal(received[0].body[0].title, 'test event');
33
33
  });
34
+ const event = { title: 'dropped', topic: { key: 'test' }, sender: { type: 'user', id: 'user1' } };
35
+ it('should drop events silently when the events service has no url', async () => {
36
+ const queue = new EventsQueue();
37
+ await queue.start({ eventsUrl: '', eventsSecret: 'secret' });
38
+ assert.equal(queue.options().inactive, true);
39
+ queue.pushEvent(event);
40
+ queue.pushNotification({ title: 'dropped', topic: { key: 'test' } });
41
+ assert.equal(queue.eventsQueue.length, 0);
42
+ assert.equal(queue.notificationsQueue.length, 0);
43
+ await queue.stop();
44
+ });
45
+ it('should drop events when the url is set but the secret is missing', async () => {
46
+ const queue = new EventsQueue();
47
+ await queue.start({ eventsUrl: `http://127.0.0.1:${port}` });
48
+ assert.equal(queue.options().inactive, true);
49
+ queue.pushEvent(event);
50
+ assert.equal(queue.eventsQueue.length, 0);
51
+ await queue.stop();
52
+ });
53
+ it('should still refuse events before start', () => {
54
+ assert.throws(() => new EventsQueue().pushEvent(event), /not initialized/);
55
+ });
34
56
  });
35
57
  //# sourceMappingURL=events-queue.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"events-queue.test.js","sourceRoot":"","sources":["events-queue.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACvD,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,MAAmB,CAAA;IACvB,IAAI,IAAY,CAAA;IAChB,MAAM,QAAQ,GAAkC,EAAE,CAAA;IAElD,MAAM,CAAC,KAAK,IAAI,EAAE;QAChB,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACtC,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,IAAI,IAAI,KAAK,CAAA,CAAC,CAAC,CAAC,CAAA;YAC1C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBACvD,GAAG,CAAC,GAAG,EAAE,CAAA;YACX,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACF,MAAM,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAA;QAC1E,IAAI,GAAI,MAAM,CAAC,OAAO,EAAkB,CAAC,IAAI,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,KAAK,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;IAE/B,mGAAmG;IACnG,qEAAqE;IACrE,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,2CAA2C,CAAC,CAAA;QACpF,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAA;QAC/B,KAAK,CAAC,QAAQ,GAAG,EAAE,SAAS,EAAE,oBAAoB,IAAI,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAA;QAClF,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAS,CAAC,CAAA;QAC9G,MAAM,KAAK,CAAC,KAAK,EAAE,CAAA;QACnB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAChC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;QAC5C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { describe, it, before, after } from 'node:test'\nimport { strict as assert } from 'assert'\nimport http from 'node:http'\nimport type { AddressInfo } from 'node:net'\nimport { EventsQueue } from './events-queue.js'\n\ndescribe('events queue', () => {\n let server: http.Server\n let port: number\n const received: { url?: string, body: any }[] = []\n\n before(async () => {\n server = http.createServer((req, res) => {\n let body = ''\n req.on('data', chunk => { body += chunk })\n req.on('end', () => {\n received.push({ url: req.url, body: JSON.parse(body) })\n res.end()\n })\n })\n await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve))\n port = (server.address() as AddressInfo).port\n })\n\n after(() => { server.close() })\n\n // the events service is configured, not user provided: it must stay reachable on a private address\n // even though the default http agents refuse those (SSRF protection)\n it('should push events to a private events service', async () => {\n assert.ok(!process.env.SSRF_PUBLIC_IPS, 'the test must run without SSRF_PUBLIC_IPS')\n const queue = new EventsQueue()\n queue._options = { eventsUrl: `http://127.0.0.1:${port}`, eventsSecret: 'secret' }\n queue.pushEvent({ title: 'test event', topic: { key: 'test' }, sender: { type: 'user', id: 'user1' } } as any)\n await queue.drain()\n assert.equal(received.length, 1)\n assert.equal(received[0].url, '/api/events')\n assert.equal(received[0].body[0].title, 'test event')\n })\n})\n"]}
1
+ {"version":3,"file":"events-queue.test.js","sourceRoot":"","sources":["events-queue.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, before, after } from 'node:test'\nimport { strict as assert } from 'assert'\nimport http from 'node:http'\nimport type { AddressInfo } from 'node:net'\nimport { EventsQueue } from './events-queue.js'\n\ndescribe('events queue', () => {\n let server: http.Server\n let port: number\n const received: { url?: string, body: any }[] = []\n\n before(async () => {\n server = http.createServer((req, res) => {\n let body = ''\n req.on('data', chunk => { body += chunk })\n req.on('end', () => {\n received.push({ url: req.url, body: JSON.parse(body) })\n res.end()\n })\n })\n await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve))\n port = (server.address() as AddressInfo).port\n })\n\n after(() => { server.close() })\n\n // the events service is configured, not user provided: it must stay reachable on a private address\n // even though the default http agents refuse those (SSRF protection)\n it('should push events to a private events service', async () => {\n assert.ok(!process.env.SSRF_PUBLIC_IPS, 'the test must run without SSRF_PUBLIC_IPS')\n const queue = new EventsQueue()\n queue._options = { eventsUrl: `http://127.0.0.1:${port}`, eventsSecret: 'secret' }\n queue.pushEvent({ title: 'test event', topic: { key: 'test' }, sender: { type: 'user', id: 'user1' } } as any)\n await queue.drain()\n assert.equal(received.length, 1)\n assert.equal(received[0].url, '/api/events')\n assert.equal(received[0].body[0].title, 'test event')\n })\n\n const event = { title: 'dropped', topic: { key: 'test' }, sender: { type: 'user', id: 'user1' } } as any\n\n it('should drop events silently when the events service has no url', async () => {\n const queue = new EventsQueue()\n await queue.start({ eventsUrl: '', eventsSecret: 'secret' })\n assert.equal(queue.options().inactive, true)\n queue.pushEvent(event)\n queue.pushNotification({ title: 'dropped', topic: { key: 'test' } } as any)\n assert.equal(queue.eventsQueue.length, 0)\n assert.equal(queue.notificationsQueue.length, 0)\n await queue.stop()\n })\n\n it('should drop events when the url is set but the secret is missing', async () => {\n const queue = new EventsQueue()\n await queue.start({ eventsUrl: `http://127.0.0.1:${port}` })\n assert.equal(queue.options().inactive, true)\n queue.pushEvent(event)\n assert.equal(queue.eventsQueue.length, 0)\n await queue.stop()\n })\n\n it('should still refuse events before start', () => {\n assert.throws(() => new EventsQueue().pushEvent(event), /not initialized/)\n })\n})\n"]}
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "@data-fair/lib-node",
3
- "version": "2.16.0",
3
+ "version": "2.17.0",
4
4
  "description": "Small nodejs related utility functions for the data-fair stack.",
5
- "main": "index.js",
6
5
  "files": [
7
6
  "**/*.js",
8
7
  "**/*.js.map",
package/session.d.ts CHANGED
@@ -22,5 +22,5 @@ export declare class SessionHandler {
22
22
  private buildTokenSessionState;
23
23
  private assertScopeAware;
24
24
  readStateFromCookie(rawCookies: string | Record<string, string | undefined> | undefined, onlyDecode?: boolean, clearBrokenSession?: () => void): Promise<SessionState>;
25
- readStateFromBearer(token: string, lang?: string): Promise<SessionState>;
25
+ readStateFromBearer(token: string, lang?: string, audiences?: string[]): Promise<SessionState>;
26
26
  }
package/session.js CHANGED
@@ -238,7 +238,10 @@ export class SessionHandler {
238
238
  // A missing organizations key is not an access token either.
239
239
  // raw reader, applies neither the route scope guard nor the session cache: not for authorization,
240
240
  // use session.req() / reqSession(). It only refuses scoped states when the handler is not scope-aware.
241
- async readStateFromBearer(token, lang) {
241
+ // The token must be addressed to the site it is used on (RFC 9068 section 4, RFC 8707): its aud
242
+ // has to share a value with `audiences`, so a token minted for one site or resource is useless on another.
243
+ // The RFC 9068 claims are moved out of the user into state.accessToken.
244
+ async readStateFromBearer(token, lang, audiences) {
242
245
  let decoded;
243
246
  try {
244
247
  decoded = decodeToken(token);
@@ -263,8 +266,22 @@ export class SessionHandler {
263
266
  throw httpError(401, 'bearer token is not an access token');
264
267
  if (user.organizations.length > 1)
265
268
  throw httpError(401, 'access token must be bound to at most one organization');
269
+ if (!audiences)
270
+ throw httpError(401, 'access token audience cannot be checked');
271
+ const { iss, sub, aud, client_id: clientId, jti, nbf, ...claims } = user;
272
+ if (aud === undefined || aud === null)
273
+ throw httpError(401, 'access token has no audience');
274
+ const audience = Array.isArray(aud) ? aud : [aud];
275
+ if (!audience.every(a => typeof a === 'string') || !audience.some(a => audiences.includes(a)))
276
+ throw httpError(401, 'access token audience mismatch');
277
+ user = claims;
266
278
  const org = user.organizations[0];
267
279
  const state = Object.assign({ lang: lang ?? this.defaultLang }, this.buildTokenSessionState(user, org?.id ?? '', org?.department ?? '', ''));
280
+ state.accessToken = { audience };
281
+ if (typeof iss === 'string')
282
+ state.accessToken.issuer = iss;
283
+ if (typeof clientId === 'string')
284
+ state.accessToken.clientId = clientId;
268
285
  this.assertScopeAware(state);
269
286
  return state;
270
287
  }
package/session.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionState, User } from '@data-fair/lib-common-types/session/index.js'\nimport { parseScope } from '@data-fair/lib-common-types/session/index.js'\nimport { createPublicKey, type KeyObject } from 'node:crypto'\nimport type { JwtHeader } from 'jsonwebtoken'\nimport jwt from 'jsonwebtoken'\nimport JwksClient from 'jwks-rsa'\nimport cookie from 'cookie'\nimport { httpError } from '@data-fair/lib-utils/http-errors.js'\n\nexport type KeySet = 'session' | 'scoped'\n// simple-directory names the keys of its scoped key set scoped-<kid>; the signature still has to\n// verify against that set, so a forged kid gains nothing\nexport const keySetOfKid = (kid?: string): KeySet => kid?.startsWith('scoped-') ? 'scoped' : 'session'\n\n// only a genuine token failure means the credential is bad; anything else (jwks unreachable,\n// jwks-rsa rate limit, network error, unexpected bug) says nothing about the token and must\n// neither refuse it as invalid nor clear the session of the user\nconst tokenFailureNames = new Set(['JsonWebTokenError', 'TokenExpiredError', 'NotBeforeError', 'SigningKeyNotFoundError'])\nconst isTokenFailure = (err: any) =>\n err?.status === 401 || tokenFailureNames.has(err?.name) ||\n err?.message === 'failed to decode token' || err?.message === 'not a session token'\n\n// jwt.decode throws a raw SyntaxError on a payload that is not JSON: that is a malformed token,\n// not an infrastructure failure\nconst decodeToken = (token: string) => {\n let decoded\n try { decoded = jwt.decode(token, { complete: true }) } catch { throw new Error('failed to decode token') }\n if (!decoded) throw new Error('failed to decode token')\n return decoded\n}\n// RFC 7515 4.1.9: the media type comparison is case-insensitive and the application/ prefix is optional\nconst isAccessTokenTyp = (typ?: string) => typeof typ === 'string' && typ.toLowerCase().replace(/^application\\//, '') === 'at+jwt'\n\ntype TokenSessionState = Omit<SessionState, 'lang' | 'dark'>\n\nexport class SessionHandler {\n private _jwksClients: Record<KeySet, JwksClient.JwksClient> | undefined\n public defaultLang: string = 'fr'\n // a service declares itself scope-aware once it enforces scopes on its routes; until then a\n // scope-restricted session (an NHI or OAuth access token with a scope claim) is refused with\n // a 403 instead of acting with the full role of its identity\n public scopeAware = false\n\n get jwksClients () {\n if (!this._jwksClients) throw new Error('session management was not initialized')\n return this._jwksClients\n }\n\n initJWKS (directoryUrl = 'http://simple-directory:8080') {\n const client = (path: string) => JwksClient({ jwksUri: directoryUrl + path })\n this._jwksClients = {\n session: client('/.well-known/jwks.json'),\n scoped: client('/.well-known/scoped-jwks.json')\n }\n // the parsed key cache is derived from these jwks clients (which enforce key rotation\n // through their own ttl), so it must be dropped whenever the clients are recreated\n this._signingKeyObjects.clear()\n }\n\n // jwks-rsa caches the JWK per kid but it exports a new PEM string at every access\n // and jsonwebtoken parses that PEM again at every verification: caching the parsed\n // KeyObject per key set and kid skips both costs.\n // IMPORTANT: getSigningKey must still be awaited on every verification. It is the only\n // thing that re-checks the kid against the current keyset, so a key rotated out of the\n // JWKS stops being accepted. Short-circuiting it from a persistent cache would accept a\n // rotated-out key forever (the lib#41 regression).\n private _signingKeyObjects = new Map<string, KeyObject>()\n protected async getSigningKeyObject (keySet: KeySet, kid?: string): Promise<KeyObject> {\n const signingKey = await this.jwksClients[keySet].getSigningKey(kid)\n const cacheKey = keySet + ':' + (kid ?? '')\n let keyObject = this._signingKeyObjects.get(cacheKey)\n if (!keyObject) {\n keyObject = createPublicKey(signingKey.getPublicKey())\n this._signingKeyObjects.set(cacheKey, keyObject)\n }\n return keyObject\n }\n\n // verifies against the key set designated by the kid: a token claiming a scoped kid must be\n // signed by a key of the scoped set, any other must be signed by a key of the session set\n async verifyTokenKeySet (token: string): Promise<{ payload: any, header: JwtHeader, keySet: KeySet }> {\n const decoded = decodeToken(token)\n const keySet = keySetOfKid(decoded.header.kid)\n const signingKeyObject = await this.getSigningKeyObject(keySet, decoded.header.kid)\n const payload = jwt.verify(token, signingKeyObject, { algorithms: ['RS256'] })\n return { payload, header: decoded.header, keySet }\n }\n\n // used for the main session token, but can also be used to use the jwks to verify other types of tokens\n // (simple-directory verifies its exchange tokens with it): only the session key set is accepted\n async verifyToken (token: string): Promise<any> {\n const { payload, keySet } = await this.verifyTokenKeySet(token)\n if (keySet !== 'session') throw new Error('not a session token')\n return payload\n }\n\n private assertTokenKind (path: 'bearer' | 'cookie', header: JwtHeader, payload: any, keySet: KeySet) {\n if (path === 'bearer') {\n if (!isAccessTokenTyp(header.typ)) throw httpError(401, 'bearer token is not an access token')\n if (keySet !== 'scoped') throw httpError(401, 'access token must be signed by the scoped key set')\n } else {\n if (isAccessTokenTyp(header.typ)) throw httpError(401, 'an access token is not a session token')\n const hasScope = payload && typeof payload === 'object' && 'scope' in payload\n if (keySet === 'session' && hasScope) throw httpError(401, 'session key set token must not carry a scope')\n if (keySet === 'scoped' && !hasScope) throw httpError(401, 'scoped key set token must carry a scope')\n if (keySet === 'scoped' && payload?.organizations?.length > 1) throw httpError(401, 'scoped token must be bound to at most one organization')\n }\n if (keySet === 'scoped') {\n if (payload && ('adminMode' in payload || 'asAdmin' in payload || 'isAdmin' in payload)) throw httpError(401, 'scoped token cannot carry superadmin claims')\n if (typeof payload?.exp !== 'number') throw httpError(401, 'scoped token must expire')\n }\n }\n\n private buildTokenSessionState (user: User, organizationId: string, departmentId: string, switchedRole: string): TokenSessionState {\n const state: TokenSessionState = {}\n\n // this is to prevent null values that are put by SD versions that do not strictly respect their schema\n for (const org of user.organizations) {\n if (!org.department) {\n delete org.department\n delete org.departmentName\n }\n }\n\n state.user = user\n // absent claim = unrestricted first party session; anything but a string is malformed\n // (null must not silently become unrestricted)\n if (user.scope !== undefined) {\n if (typeof user.scope !== 'string') throw httpError(401, 'invalid scope claim')\n state.scopes = parseScope(user.scope)\n }\n\n if (organizationId) {\n state.organization = user.organizations.find(o => {\n if (o.id !== organizationId) return false\n if (departmentId && departmentId !== o.department) return false\n if (switchedRole && switchedRole !== o.role) return false\n return true\n })\n }\n if (state.organization) {\n state.account = {\n type: 'organization',\n id: state.organization.id,\n name: state.organization.name,\n department: state.organization.department,\n departmentName: state.organization.departmentName\n }\n state.accountRole = state.organization.role\n } else {\n state.account = {\n type: 'user',\n id: user.id,\n name: user.name\n }\n state.accountRole = 'admin'\n return state\n }\n\n if (state.user?.siteOwner) {\n if (state.user.siteOwner.type === 'user' && state.user.siteOwner.id === state.user.id) {\n state.siteRole = 'admin'\n }\n if (state.user.siteOwner.type === 'organization' && state.user.siteOwner.id === state.organization?.id) {\n state.siteRole = state.organization.role\n }\n }\n return state\n }\n\n private assertScopeAware (state: SessionState) {\n if (state.scopes && !this.scopeAware) throw httpError(403, 'this service does not accept scope-restricted sessions')\n }\n\n // accepts either the raw Cookie header or an already-parsed cookies object\n // (e.g. req.cookies when a cookie-parser middleware already ran upstream) to\n // avoid re-parsing and allocating a second cookies object per request\n // raw reader, applies neither the route scope guard nor the session cache: not for authorization,\n // use session.req() / reqSession(). It only refuses scoped states when the handler is not scope-aware.\n async readStateFromCookie (rawCookies: string | Record<string, string | undefined> | undefined, onlyDecode?: boolean, clearBrokenSession?: () => void): Promise<SessionState> {\n if (!rawCookies) return { lang: this.defaultLang }\n const cookies = typeof rawCookies === 'string' ? cookie.parse(rawCookies) : rawCookies\n\n const lang = cookies.i18n_lang || this.defaultLang\n const dark = cookies.theme_dark === '1' || cookies.theme_dark === 'true'\n\n if (!cookies.id_token) return { lang, dark }\n if (!cookies.id_token_sign && !onlyDecode) return { lang, dark }\n const token = cookies.id_token + '.' + (cookies.id_token_sign ?? '')\n const organizationId = cookies.id_token_org ?? ''\n const departmentId = cookies.id_token_dep ?? ''\n const switchedRole = cookies.id_token_role ?? ''\n\n let user: User\n if (onlyDecode) {\n let decoded\n try {\n decoded = decodeToken(token)\n this.assertTokenKind('cookie', decoded.header, decoded.payload, keySetOfKid(decoded.header.kid))\n } catch (err: any) {\n if (clearBrokenSession) clearBrokenSession()\n throw err.status === 401 ? err : httpError(401)\n }\n user = decoded.payload as User\n } else {\n try {\n const verified = await this.verifyTokenKeySet(token)\n user = verified.payload as User\n this.assertTokenKind('cookie', verified.header, verified.payload, verified.keySet)\n } catch (err: any) {\n console.warn(err)\n if (!isTokenFailure(err)) {\n // happens in case of temporary unavailability of SD\n // better not to disconnect user in this case\n throw httpError(500, 'Session token public keys not initialized')\n }\n if (clearBrokenSession) clearBrokenSession()\n throw err.status === 401 ? err : httpError(401)\n }\n }\n if (!user) return { lang, dark }\n const state = Object.assign({ lang, dark } as SessionState, this.buildTokenSessionState(user, organizationId, departmentId, switchedRole))\n this.assertScopeAware(state)\n return state\n }\n\n // Only tokens minted as access tokens (RFC 9068 header typ at+jwt) are bearer credentials.\n // Session cookie tokens and the long lived exchange token are signed by the same keys and\n // must never be usable this way (cf simple-directory docs/architecture/session-theft-protections.md).\n // An access token is bound to one account: its organizations array holds the one membership it\n // acts in (1 = that organization), or nothing for the personal account (0 memberships). More than\n // one is refused because the full array stays in user.organizations and consumers filter on it.\n // A missing organizations key is not an access token either.\n // raw reader, applies neither the route scope guard nor the session cache: not for authorization,\n // use session.req() / reqSession(). It only refuses scoped states when the handler is not scope-aware.\n async readStateFromBearer (token: string, lang?: string): Promise<SessionState> {\n let decoded\n try { decoded = decodeToken(token) } catch { throw httpError(401, 'bearer token is not an access token') }\n if (!isAccessTokenTyp(decoded.header.typ)) throw httpError(401, 'bearer token is not an access token')\n let user: User\n try {\n const verified = await this.verifyTokenKeySet(token)\n this.assertTokenKind('bearer', verified.header, verified.payload, verified.keySet)\n user = verified.payload as User\n } catch (err: any) {\n if (!isTokenFailure(err)) throw httpError(500, 'Session token public keys not initialized')\n throw err.status === 401 ? err : httpError(401)\n }\n if (!user?.organizations) throw httpError(401, 'bearer token is not an access token')\n if (user.organizations.length > 1) throw httpError(401, 'access token must be bound to at most one organization')\n const org = user.organizations[0]\n const state = Object.assign({ lang: lang ?? this.defaultLang } as SessionState, this.buildTokenSessionState(user, org?.id ?? '', org?.department ?? '', ''))\n this.assertScopeAware(state)\n return state\n }\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionState, User } from '@data-fair/lib-common-types/session/index.js'\nimport { parseScope } from '@data-fair/lib-common-types/session/index.js'\nimport { createPublicKey, type KeyObject } from 'node:crypto'\nimport type { JwtHeader } from 'jsonwebtoken'\nimport jwt from 'jsonwebtoken'\nimport JwksClient from 'jwks-rsa'\nimport cookie from 'cookie'\nimport { httpError } from '@data-fair/lib-utils/http-errors.js'\n\nexport type KeySet = 'session' | 'scoped'\n// simple-directory names the keys of its scoped key set scoped-<kid>; the signature still has to\n// verify against that set, so a forged kid gains nothing\nexport const keySetOfKid = (kid?: string): KeySet => kid?.startsWith('scoped-') ? 'scoped' : 'session'\n\n// only a genuine token failure means the credential is bad; anything else (jwks unreachable,\n// jwks-rsa rate limit, network error, unexpected bug) says nothing about the token and must\n// neither refuse it as invalid nor clear the session of the user\nconst tokenFailureNames = new Set(['JsonWebTokenError', 'TokenExpiredError', 'NotBeforeError', 'SigningKeyNotFoundError'])\nconst isTokenFailure = (err: any) =>\n err?.status === 401 || tokenFailureNames.has(err?.name) ||\n err?.message === 'failed to decode token' || err?.message === 'not a session token'\n\n// jwt.decode throws a raw SyntaxError on a payload that is not JSON: that is a malformed token,\n// not an infrastructure failure\nconst decodeToken = (token: string) => {\n let decoded\n try { decoded = jwt.decode(token, { complete: true }) } catch { throw new Error('failed to decode token') }\n if (!decoded) throw new Error('failed to decode token')\n return decoded\n}\n// RFC 7515 4.1.9: the media type comparison is case-insensitive and the application/ prefix is optional\nconst isAccessTokenTyp = (typ?: string) => typeof typ === 'string' && typ.toLowerCase().replace(/^application\\//, '') === 'at+jwt'\n\ntype TokenSessionState = Omit<SessionState, 'lang' | 'dark'>\n\nexport class SessionHandler {\n private _jwksClients: Record<KeySet, JwksClient.JwksClient> | undefined\n public defaultLang: string = 'fr'\n // a service declares itself scope-aware once it enforces scopes on its routes; until then a\n // scope-restricted session (an NHI or OAuth access token with a scope claim) is refused with\n // a 403 instead of acting with the full role of its identity\n public scopeAware = false\n\n get jwksClients () {\n if (!this._jwksClients) throw new Error('session management was not initialized')\n return this._jwksClients\n }\n\n initJWKS (directoryUrl = 'http://simple-directory:8080') {\n const client = (path: string) => JwksClient({ jwksUri: directoryUrl + path })\n this._jwksClients = {\n session: client('/.well-known/jwks.json'),\n scoped: client('/.well-known/scoped-jwks.json')\n }\n // the parsed key cache is derived from these jwks clients (which enforce key rotation\n // through their own ttl), so it must be dropped whenever the clients are recreated\n this._signingKeyObjects.clear()\n }\n\n // jwks-rsa caches the JWK per kid but it exports a new PEM string at every access\n // and jsonwebtoken parses that PEM again at every verification: caching the parsed\n // KeyObject per key set and kid skips both costs.\n // IMPORTANT: getSigningKey must still be awaited on every verification. It is the only\n // thing that re-checks the kid against the current keyset, so a key rotated out of the\n // JWKS stops being accepted. Short-circuiting it from a persistent cache would accept a\n // rotated-out key forever (the lib#41 regression).\n private _signingKeyObjects = new Map<string, KeyObject>()\n protected async getSigningKeyObject (keySet: KeySet, kid?: string): Promise<KeyObject> {\n const signingKey = await this.jwksClients[keySet].getSigningKey(kid)\n const cacheKey = keySet + ':' + (kid ?? '')\n let keyObject = this._signingKeyObjects.get(cacheKey)\n if (!keyObject) {\n keyObject = createPublicKey(signingKey.getPublicKey())\n this._signingKeyObjects.set(cacheKey, keyObject)\n }\n return keyObject\n }\n\n // verifies against the key set designated by the kid: a token claiming a scoped kid must be\n // signed by a key of the scoped set, any other must be signed by a key of the session set\n async verifyTokenKeySet (token: string): Promise<{ payload: any, header: JwtHeader, keySet: KeySet }> {\n const decoded = decodeToken(token)\n const keySet = keySetOfKid(decoded.header.kid)\n const signingKeyObject = await this.getSigningKeyObject(keySet, decoded.header.kid)\n const payload = jwt.verify(token, signingKeyObject, { algorithms: ['RS256'] })\n return { payload, header: decoded.header, keySet }\n }\n\n // used for the main session token, but can also be used to use the jwks to verify other types of tokens\n // (simple-directory verifies its exchange tokens with it): only the session key set is accepted\n async verifyToken (token: string): Promise<any> {\n const { payload, keySet } = await this.verifyTokenKeySet(token)\n if (keySet !== 'session') throw new Error('not a session token')\n return payload\n }\n\n private assertTokenKind (path: 'bearer' | 'cookie', header: JwtHeader, payload: any, keySet: KeySet) {\n if (path === 'bearer') {\n if (!isAccessTokenTyp(header.typ)) throw httpError(401, 'bearer token is not an access token')\n if (keySet !== 'scoped') throw httpError(401, 'access token must be signed by the scoped key set')\n } else {\n if (isAccessTokenTyp(header.typ)) throw httpError(401, 'an access token is not a session token')\n const hasScope = payload && typeof payload === 'object' && 'scope' in payload\n if (keySet === 'session' && hasScope) throw httpError(401, 'session key set token must not carry a scope')\n if (keySet === 'scoped' && !hasScope) throw httpError(401, 'scoped key set token must carry a scope')\n if (keySet === 'scoped' && payload?.organizations?.length > 1) throw httpError(401, 'scoped token must be bound to at most one organization')\n }\n if (keySet === 'scoped') {\n if (payload && ('adminMode' in payload || 'asAdmin' in payload || 'isAdmin' in payload)) throw httpError(401, 'scoped token cannot carry superadmin claims')\n if (typeof payload?.exp !== 'number') throw httpError(401, 'scoped token must expire')\n }\n }\n\n private buildTokenSessionState (user: User, organizationId: string, departmentId: string, switchedRole: string): TokenSessionState {\n const state: TokenSessionState = {}\n\n // this is to prevent null values that are put by SD versions that do not strictly respect their schema\n for (const org of user.organizations) {\n if (!org.department) {\n delete org.department\n delete org.departmentName\n }\n }\n\n state.user = user\n // absent claim = unrestricted first party session; anything but a string is malformed\n // (null must not silently become unrestricted)\n if (user.scope !== undefined) {\n if (typeof user.scope !== 'string') throw httpError(401, 'invalid scope claim')\n state.scopes = parseScope(user.scope)\n }\n\n if (organizationId) {\n state.organization = user.organizations.find(o => {\n if (o.id !== organizationId) return false\n if (departmentId && departmentId !== o.department) return false\n if (switchedRole && switchedRole !== o.role) return false\n return true\n })\n }\n if (state.organization) {\n state.account = {\n type: 'organization',\n id: state.organization.id,\n name: state.organization.name,\n department: state.organization.department,\n departmentName: state.organization.departmentName\n }\n state.accountRole = state.organization.role\n } else {\n state.account = {\n type: 'user',\n id: user.id,\n name: user.name\n }\n state.accountRole = 'admin'\n return state\n }\n\n if (state.user?.siteOwner) {\n if (state.user.siteOwner.type === 'user' && state.user.siteOwner.id === state.user.id) {\n state.siteRole = 'admin'\n }\n if (state.user.siteOwner.type === 'organization' && state.user.siteOwner.id === state.organization?.id) {\n state.siteRole = state.organization.role\n }\n }\n return state\n }\n\n private assertScopeAware (state: SessionState) {\n if (state.scopes && !this.scopeAware) throw httpError(403, 'this service does not accept scope-restricted sessions')\n }\n\n // accepts either the raw Cookie header or an already-parsed cookies object\n // (e.g. req.cookies when a cookie-parser middleware already ran upstream) to\n // avoid re-parsing and allocating a second cookies object per request\n // raw reader, applies neither the route scope guard nor the session cache: not for authorization,\n // use session.req() / reqSession(). It only refuses scoped states when the handler is not scope-aware.\n async readStateFromCookie (rawCookies: string | Record<string, string | undefined> | undefined, onlyDecode?: boolean, clearBrokenSession?: () => void): Promise<SessionState> {\n if (!rawCookies) return { lang: this.defaultLang }\n const cookies = typeof rawCookies === 'string' ? cookie.parse(rawCookies) : rawCookies\n\n const lang = cookies.i18n_lang || this.defaultLang\n const dark = cookies.theme_dark === '1' || cookies.theme_dark === 'true'\n\n if (!cookies.id_token) return { lang, dark }\n if (!cookies.id_token_sign && !onlyDecode) return { lang, dark }\n const token = cookies.id_token + '.' + (cookies.id_token_sign ?? '')\n const organizationId = cookies.id_token_org ?? ''\n const departmentId = cookies.id_token_dep ?? ''\n const switchedRole = cookies.id_token_role ?? ''\n\n let user: User\n if (onlyDecode) {\n let decoded\n try {\n decoded = decodeToken(token)\n this.assertTokenKind('cookie', decoded.header, decoded.payload, keySetOfKid(decoded.header.kid))\n } catch (err: any) {\n if (clearBrokenSession) clearBrokenSession()\n throw err.status === 401 ? err : httpError(401)\n }\n user = decoded.payload as User\n } else {\n try {\n const verified = await this.verifyTokenKeySet(token)\n user = verified.payload as User\n this.assertTokenKind('cookie', verified.header, verified.payload, verified.keySet)\n } catch (err: any) {\n console.warn(err)\n if (!isTokenFailure(err)) {\n // happens in case of temporary unavailability of SD\n // better not to disconnect user in this case\n throw httpError(500, 'Session token public keys not initialized')\n }\n if (clearBrokenSession) clearBrokenSession()\n throw err.status === 401 ? err : httpError(401)\n }\n }\n if (!user) return { lang, dark }\n const state = Object.assign({ lang, dark } as SessionState, this.buildTokenSessionState(user, organizationId, departmentId, switchedRole))\n this.assertScopeAware(state)\n return state\n }\n\n // Only tokens minted as access tokens (RFC 9068 header typ at+jwt) are bearer credentials.\n // Session cookie tokens and the long lived exchange token are signed by the same keys and\n // must never be usable this way (cf simple-directory docs/architecture/session-theft-protections.md).\n // An access token is bound to one account: its organizations array holds the one membership it\n // acts in (1 = that organization), or nothing for the personal account (0 memberships). More than\n // one is refused because the full array stays in user.organizations and consumers filter on it.\n // A missing organizations key is not an access token either.\n // raw reader, applies neither the route scope guard nor the session cache: not for authorization,\n // use session.req() / reqSession(). It only refuses scoped states when the handler is not scope-aware.\n // The token must be addressed to the site it is used on (RFC 9068 section 4, RFC 8707): its aud\n // has to share a value with `audiences`, so a token minted for one site or resource is useless on another.\n // The RFC 9068 claims are moved out of the user into state.accessToken.\n async readStateFromBearer (token: string, lang?: string, audiences?: string[]): Promise<SessionState> {\n let decoded\n try { decoded = decodeToken(token) } catch { throw httpError(401, 'bearer token is not an access token') }\n if (!isAccessTokenTyp(decoded.header.typ)) throw httpError(401, 'bearer token is not an access token')\n let user: User\n try {\n const verified = await this.verifyTokenKeySet(token)\n this.assertTokenKind('bearer', verified.header, verified.payload, verified.keySet)\n user = verified.payload as User\n } catch (err: any) {\n if (!isTokenFailure(err)) throw httpError(500, 'Session token public keys not initialized')\n throw err.status === 401 ? err : httpError(401)\n }\n if (!user?.organizations) throw httpError(401, 'bearer token is not an access token')\n if (user.organizations.length > 1) throw httpError(401, 'access token must be bound to at most one organization')\n if (!audiences) throw httpError(401, 'access token audience cannot be checked')\n const { iss, sub, aud, client_id: clientId, jti, nbf, ...claims } = user as any\n if (aud === undefined || aud === null) throw httpError(401, 'access token has no audience')\n const audience: string[] = Array.isArray(aud) ? aud : [aud]\n if (!audience.every(a => typeof a === 'string') || !audience.some(a => audiences.includes(a))) throw httpError(401, 'access token audience mismatch')\n user = claims as User\n const org = user.organizations[0]\n const state = Object.assign({ lang: lang ?? this.defaultLang } as SessionState, this.buildTokenSessionState(user, org?.id ?? '', org?.department ?? '', ''))\n state.accessToken = { audience }\n if (typeof iss === 'string') state.accessToken.issuer = iss\n if (typeof clientId === 'string') state.accessToken.clientId = clientId\n this.assertScopeAware(state)\n return state\n }\n}\n"]}
package/session.test.js CHANGED
@@ -182,9 +182,12 @@ const signWith = (privateKey, kid, payload, header = {}, opts = {}) => jwt.sign(
182
182
  const signSession = (payload, header, opts) => signWith(sessionKeys.privateKey, 'sd-1', payload, header, opts);
183
183
  const signScoped = (payload, header, opts) => signWith(scopedKeys.privateKey, 'scoped-sd-1', payload, header, opts);
184
184
  // real jwt verification against two local key pairs, one per key set
185
- const realHandler = (scopeAware = true) => {
185
+ const realHandler = (scopeAware = true, siteAudiences = ['https://x']) => {
186
186
  const handler = new SessionHandler();
187
187
  handler.scopeAware = scopeAware;
188
+ // the tests read bearers as if on the site https://x unless told otherwise
189
+ const readStateFromBearer = handler.readStateFromBearer.bind(handler);
190
+ handler.readStateFromBearer = (token, lang) => readStateFromBearer(token, lang, siteAudiences ?? undefined);
188
191
  handler.getSigningKeyObject = async (keySet) => keySet === 'scoped' ? scopedKeys.publicKey : sessionKeys.publicKey;
189
192
  return handler;
190
193
  };
@@ -194,7 +197,7 @@ const toCookies = (token) => {
194
197
  };
195
198
  const rejects401 = (p) => assert.rejects(p, (err) => err.status === 401);
196
199
  describe('SessionHandler.readStateFromBearer', () => {
197
- const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read organizations:read' };
200
+ const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read organizations:read', iss: 'https://x/simple-directory', aud: 'https://x', client_id: 'nhi-a', jti: 'j1' };
198
201
  const bearer = (payload, header = { typ: 'at+jwt' }, opts) => signScoped(payload, header, opts);
199
202
  it('reads an at+jwt access token, bound to its single organization, with its scopes', async () => {
200
203
  const state = await realHandler().readStateFromBearer(bearer(nhiUser));
@@ -208,14 +211,14 @@ describe('SessionHandler.readStateFromBearer', () => {
208
211
  await rejects401(realHandler().readStateFromBearer(bearer(testUser)));
209
212
  });
210
213
  it('gives a personal account session to a token with no organization membership', async () => {
211
- const state = await realHandler().readStateFromBearer(bearer({ ...testUser, organizations: [], scope: 'datasets' }));
214
+ const state = await realHandler().readStateFromBearer(bearer({ ...testUser, organizations: [], scope: 'datasets', aud: 'https://x' }));
212
215
  assert.equal(state.account?.type, 'user');
213
216
  assert.equal(state.account?.id, 'user1');
214
217
  assert.equal(state.accountRole, 'admin');
215
218
  });
216
219
  it('refuses a token without an organizations key', async () => {
217
220
  const { organizations, ...noOrgs } = testUser;
218
- await rejects401(realHandler().readStateFromBearer(bearer({ ...noOrgs, scope: 'datasets' })));
221
+ await rejects401(realHandler().readStateFromBearer(bearer({ ...noOrgs, scope: 'datasets', aud: 'https://x' })));
219
222
  });
220
223
  it('refuses (401) an invalid scope claim on the bearer and cookie paths', async () => {
221
224
  for (const scope of [null, 12, ['a']]) {
@@ -249,13 +252,34 @@ describe('SessionHandler.readStateFromBearer', () => {
249
252
  await rejects401(handler.readStateFromBearer(signSession({ user: 'user1', session: 'abc' }, { typ: 'JWT' })));
250
253
  await rejects401(handler.readStateFromBearer('not-a-jwt'));
251
254
  });
255
+ it('bearer with matching aud string is accepted and claims move to accessToken', async () => {
256
+ const state = await realHandler().readStateFromBearer(bearer(nhiUser));
257
+ assert.deepEqual(state.accessToken, { issuer: 'https://x/simple-directory', clientId: 'nhi-a', audience: ['https://x'] });
258
+ for (const claim of ['iss', 'sub', 'aud', 'client_id', 'jti', 'nbf'])
259
+ assert.equal(claim in state.user, false, claim);
260
+ });
261
+ it('bearer with aud array containing the site is accepted', async () => {
262
+ const state = await realHandler().readStateFromBearer(bearer({ ...nhiUser, aud: ['https://other', 'https://x'] }));
263
+ assert.deepEqual(state.accessToken?.audience, ['https://other', 'https://x']);
264
+ });
265
+ it('bearer without aud is refused 401', async () => {
266
+ const { aud, ...noAud } = nhiUser;
267
+ await rejects401(realHandler().readStateFromBearer(bearer(noAud)));
268
+ });
269
+ it('bearer with another aud is refused 401', async () => {
270
+ await rejects401(realHandler().readStateFromBearer(bearer({ ...nhiUser, aud: 'https://y' })));
271
+ await rejects401(realHandler().readStateFromBearer(bearer({ ...nhiUser, aud: ['https://y', 'https://z'] })));
272
+ });
273
+ it('bearer refused 401 when audiences are not provided', async () => {
274
+ await rejects401(realHandler(true, null).readStateFromBearer(bearer(nhiUser)));
275
+ });
252
276
  it('fills scopes from a cookie session too', async () => {
253
277
  const state = await realHandler().readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })));
254
278
  assert.deepEqual(state.scopes, ['datasets']);
255
279
  });
256
280
  });
257
281
  describe('key sets and token kinds', () => {
258
- const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read' };
282
+ const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read', aud: 'https://x' };
259
283
  it('keySetOfKid', () => {
260
284
  assert.equal(keySetOfKid('scoped-sd-1'), 'scoped');
261
285
  assert.equal(keySetOfKid('sd-1'), 'session');
@@ -370,7 +394,7 @@ describe('malformed tokens, typ normalization, one organization per scoped cooki
370
394
  });
371
395
  it('typ at+jwt is compared case-insensitively with an optional application/ prefix', async () => {
372
396
  const handler = realHandler();
373
- const nhi = { ...testUser, organizations: oneOrg, scope: 'datasets:read' };
397
+ const nhi = { ...testUser, organizations: oneOrg, scope: 'datasets:read', aud: 'https://x' };
374
398
  for (const typ of ['AT+JWT', 'application/at+jwt', 'Application/AT+JWT']) {
375
399
  assert.equal((await handler.readStateFromBearer(signScoped(nhi, { typ }))).user?.id, 'user1');
376
400
  await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhi, { typ }))));
@@ -1 +1 @@
1
- {"version":3,"file":"session.test.js","sourceRoot":"","sources":["session.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from 'node:test'\nimport { strict as assert } from 'assert'\nimport { generateKeyPairSync } from 'node:crypto'\nimport jwt from 'jsonwebtoken'\nimport { SessionHandler, keySetOfKid } from './session.js'\n\nconst testUser = {\n email: 'test@example.com',\n id: 'user1',\n name: 'Test User',\n organizations: [\n { id: 'org1', name: 'Test Org', role: 'admin' },\n { id: 'org2', name: 'Other Org', role: 'user', department: 'dep1', departmentName: 'Department 1' }\n ]\n}\n\n// a handler whose token verification is stubbed, counts the number of verifications\nconst stubbedHandler = () => {\n const handler = new SessionHandler()\n const calls = { count: 0 }\n handler.verifyTokenKeySet = async () => {\n calls.count++\n return { payload: structuredClone(testUser), header: { alg: 'RS256' }, keySet: 'session' }\n }\n return { handler, calls }\n}\n\ndescribe('SessionHandler.readStateFromCookie', () => {\n it('should return default session with no cookies', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie(undefined)\n assert.equal(state.lang, 'fr')\n assert.equal(state.user, undefined)\n assert.equal(state.dark, undefined)\n })\n\n it('should parse dark mode cookie', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('theme_dark=true')\n assert.equal(state.dark, true)\n })\n\n it('should parse i18n_lang cookie', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('i18n_lang=en')\n assert.equal(state.lang, 'en')\n })\n\n it('should use defaultLang when no i18n_lang cookie', async () => {\n const handler = new SessionHandler()\n handler.defaultLang = 'de'\n const state = await handler.readStateFromCookie('')\n assert.equal(state.lang, 'de')\n })\n\n it('should return early when no id_token', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('i18n_lang=en; theme_dark=1')\n assert.equal(state.lang, 'en')\n assert.equal(state.dark, true)\n assert.equal(state.user, undefined)\n })\n\n it('should return early when no id_token_sign and not onlyDecode', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('id_token=header.payload')\n assert.equal(state.user, undefined)\n })\n\n it('should select the organization account from cookies', async () => {\n const { handler } = stubbedHandler()\n const state = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s; id_token_org=org2; id_token_dep=dep1')\n assert.equal(state.account?.type, 'organization')\n assert.equal(state.account?.id, 'org2')\n assert.equal(state.account?.department, 'dep1')\n assert.equal(state.accountRole, 'user')\n })\n\n it('should build a user account when no org cookie', async () => {\n const { handler } = stubbedHandler()\n const state = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s')\n assert.equal(state.account?.type, 'user')\n assert.equal(state.account?.id, 'user1')\n assert.equal(state.accountRole, 'admin')\n })\n\n it('should accept an already-parsed cookies object without re-parsing', async () => {\n const { handler, calls } = stubbedHandler()\n const parsed = { id_token: 'h.p', id_token_sign: 's', id_token_org: 'org2', id_token_dep: 'dep1', i18n_lang: 'en', theme_dark: '1' }\n const state = await handler.readStateFromCookie(parsed)\n assert.equal(state.lang, 'en')\n assert.equal(state.dark, true)\n assert.equal(state.account?.type, 'organization')\n assert.equal(state.account?.id, 'org2')\n assert.equal(calls.count, 1)\n })\n\n it('should produce equivalent state from string and object cookie inputs', async () => {\n const { handler } = stubbedHandler()\n const fromString = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s; i18n_lang=en')\n const fromObject = await handler.readStateFromCookie({ id_token: 'h.p', id_token_sign: 's', i18n_lang: 'en' })\n assert.deepEqual(fromString, fromObject)\n })\n\n it('should verify the token on every request', async () => {\n const { handler, calls } = stubbedHandler()\n const cookies = 'id_token=h.p; id_token_sign=s'\n await handler.readStateFromCookie(cookies)\n await handler.readStateFromCookie(cookies)\n assert.equal(calls.count, 2)\n })\n\n it('should return a mutable state that callers can adjust', async () => {\n const { handler } = stubbedHandler()\n const state = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s')\n state.user!.name = 'mutated' // must not throw\n state.user!.organizations[0].role = 'other'\n assert.equal(state.user!.name, 'mutated')\n assert.equal(state.user!.organizations[0].role, 'other')\n })\n\n it('should decode without verifying in onlyDecode mode', async () => {\n const handler = new SessionHandler()\n const token = jwt.sign(structuredClone(testUser), 'secret')\n const [header, payload, sign] = token.split('.')\n const state = await handler.readStateFromCookie(`id_token=${header}.${payload}; id_token_sign=${sign}`, true)\n assert.equal(state.user?.id, 'user1')\n assert.equal(state.account?.type, 'user')\n })\n\n it('should reject and clear the session when the token is invalid', async () => {\n const handler = new SessionHandler()\n handler.verifyTokenKeySet = async () => { throw Object.assign(new Error('jwt expired'), { name: 'TokenExpiredError' }) }\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie('id_token=h.p; id_token_sign=s', undefined, () => { cleared = true }),\n (err: any) => err.status === 401\n )\n assert.equal(cleared, true)\n })\n\n it('should surface a 500 without clearing the session on a jwks error', async () => {\n const handler = new SessionHandler()\n handler.verifyTokenKeySet = async () => { throw Object.assign(new Error('jwks down'), { name: 'JwksError' }) }\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie('id_token=h.p; id_token_sign=s', undefined, () => { cleared = true }),\n (err: any) => err.status === 500\n )\n assert.equal(cleared, false)\n })\n})\n\ndescribe('SessionHandler.verifyToken', () => {\n it('should consult the JWKS on every verification but cache the parsed key per kid', async () => {\n const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })\n const pem = publicKey.export({ format: 'pem', type: 'spki' }) as string\n const handler = new SessionHandler()\n let getSigningKeyCalls = 0\n let getPublicKeyCalls = 0\n ;(handler as any)._jwksClients = {\n session: {\n getSigningKey: async () => {\n getSigningKeyCalls++\n return { getPublicKey: () => { getPublicKeyCalls++; return pem } }\n }\n }\n }\n const token = jwt.sign({ id: 'user1' }, privateKey, { algorithm: 'RS256', keyid: 'kid1' })\n const payload1 = await handler.verifyToken(token)\n assert.equal(payload1.id, 'user1')\n const payload2 = await handler.verifyToken(token)\n assert.equal(payload2.id, 'user1')\n // the signing key must be re-checked against the JWKS every time so that a key\n // rotated out of the keyset stops being accepted (lib#41 regression)\n assert.equal(getSigningKeyCalls, 2)\n // but the expensive PEM export + parsing is cached per kid\n assert.equal(getPublicKeyCalls, 1)\n })\n\n it('should reject a token whose signing key was rotated out of the JWKS', async () => {\n const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })\n const pem = publicKey.export({ format: 'pem', type: 'spki' }) as string\n const handler = new SessionHandler()\n let keyPresent = true\n ;(handler as any)._jwksClients = {\n session: {\n getSigningKey: async () => {\n if (!keyPresent) throw Object.assign(new Error('key not found'), { name: 'SigningKeyNotFoundError' })\n return { getPublicKey: () => pem }\n }\n }\n }\n const token = jwt.sign({ id: 'user1' }, privateKey, { algorithm: 'RS256', keyid: 'kid1' })\n // first verification succeeds and would populate any key cache\n assert.equal((await handler.verifyToken(token)).id, 'user1')\n // the key is rotated out of the published keyset\n keyPresent = false\n // a cached parsed key must not let the rotated-out key keep verifying\n await assert.rejects(handler.verifyToken(token))\n })\n})\n\nconst sessionKeys = generateKeyPairSync('rsa', { modulusLength: 2048 })\nconst scopedKeys = generateKeyPairSync('rsa', { modulusLength: 2048 })\nconst signWith = (privateKey: any, kid: string, payload: any, header: any = {}, opts: any = {}) =>\n jwt.sign(payload, privateKey, { algorithm: 'RS256', expiresIn: '5m', keyid: kid, header: { alg: 'RS256', ...header }, ...opts })\nconst signSession = (payload: any, header?: any, opts?: any) => signWith(sessionKeys.privateKey, 'sd-1', payload, header, opts)\nconst signScoped = (payload: any, header?: any, opts?: any) => signWith(scopedKeys.privateKey, 'scoped-sd-1', payload, header, opts)\n// real jwt verification against two local key pairs, one per key set\nconst realHandler = (scopeAware = true) => {\n const handler = new SessionHandler()\n handler.scopeAware = scopeAware\n ;(handler as any).getSigningKeyObject = async (keySet: string) => keySet === 'scoped' ? scopedKeys.publicKey : sessionKeys.publicKey\n return handler\n}\nconst toCookies = (token: string) => {\n const [h, p, s] = token.split('.')\n return `id_token=${h}.${p}; id_token_sign=${s}`\n}\nconst rejects401 = (p: Promise<any>) => assert.rejects(p, (err: any) => err.status === 401)\n\ndescribe('SessionHandler.readStateFromBearer', () => {\n const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read organizations:read' }\n const bearer = (payload: any, header: any = { typ: 'at+jwt' }, opts?: any) => signScoped(payload, header, opts)\n\n it('reads an at+jwt access token, bound to its single organization, with its scopes', async () => {\n const state = await realHandler().readStateFromBearer(bearer(nhiUser))\n assert.equal(state.user?.id, 'user1')\n assert.equal(state.account?.type, 'organization')\n assert.equal(state.account?.id, 'org1')\n assert.equal(state.accountRole, 'admin')\n assert.deepEqual(state.scopes, ['datasets:read', 'organizations:read'])\n })\n\n it('refuses (401) a token bound to several organizations', async () => {\n await rejects401(realHandler().readStateFromBearer(bearer(testUser)))\n })\n\n it('gives a personal account session to a token with no organization membership', async () => {\n const state = await realHandler().readStateFromBearer(bearer({ ...testUser, organizations: [], scope: 'datasets' }))\n assert.equal(state.account?.type, 'user')\n assert.equal(state.account?.id, 'user1')\n assert.equal(state.accountRole, 'admin')\n })\n\n it('refuses a token without an organizations key', async () => {\n const { organizations, ...noOrgs } = testUser\n await rejects401(realHandler().readStateFromBearer(bearer({ ...noOrgs, scope: 'datasets' })))\n })\n\n it('refuses (401) an invalid scope claim on the bearer and cookie paths', async () => {\n for (const scope of [null, 12, ['a']]) {\n await rejects401(realHandler().readStateFromBearer(bearer({ ...nhiUser, scope })))\n const handler = new SessionHandler()\n handler.scopeAware = true\n handler.verifyTokenKeySet = async () => ({ payload: structuredClone({ ...testUser, scope, exp: 4102444800 }), header: { alg: 'RS256' }, keySet: 'scoped' })\n await rejects401(handler.readStateFromCookie('id_token=a.b; id_token_sign=c'))\n }\n })\n\n it('refuses a scoped session unless the handler is scope-aware', async () => {\n const handler = realHandler(false)\n assert.equal(handler.scopeAware, false)\n await assert.rejects(handler.readStateFromBearer(bearer(nhiUser)), (err: any) => err.status === 403)\n const cookies = toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' }))\n await assert.rejects(handler.readStateFromCookie(cookies), (err: any) => err.status === 403)\n handler.scopeAware = true\n assert.deepEqual((await handler.readStateFromCookie(cookies)).scopes, ['datasets'])\n assert.deepEqual((await handler.readStateFromBearer(bearer(nhiUser))).scopes, ['datasets:read', 'organizations:read'])\n })\n\n it('refuses an expired token or one signed by another key when really verifying', async () => {\n const { privateKey: otherKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })\n const handler = realHandler()\n assert.equal((await handler.readStateFromBearer(bearer(nhiUser))).user?.id, 'user1')\n await rejects401(handler.readStateFromBearer(bearer(nhiUser, undefined, { expiresIn: -10 })))\n await rejects401(handler.readStateFromBearer(signWith(otherKey, 'scoped-sd-1', nhiUser, { typ: 'at+jwt' })))\n })\n\n it('refuses a session token or an exchange token presented as bearer', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromBearer(signSession(testUser)))\n await rejects401(handler.readStateFromBearer(signSession({ user: 'user1', session: 'abc' }, { typ: 'JWT' })))\n await rejects401(handler.readStateFromBearer('not-a-jwt'))\n })\n\n it('fills scopes from a cookie session too', async () => {\n const state = await realHandler().readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })))\n assert.deepEqual(state.scopes, ['datasets'])\n })\n})\n\ndescribe('key sets and token kinds', () => {\n const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read' }\n\n it('keySetOfKid', () => {\n assert.equal(keySetOfKid('scoped-sd-1'), 'scoped')\n assert.equal(keySetOfKid('sd-1'), 'session')\n assert.equal(keySetOfKid(undefined), 'session')\n })\n\n it('bearer requires an at+jwt from the scoped set', async () => {\n const handler = realHandler()\n assert.equal((await handler.readStateFromBearer(signScoped(nhiUser, { typ: 'at+jwt' }))).user?.id, 'user1')\n await rejects401(handler.readStateFromBearer(signSession(nhiUser, { typ: 'at+jwt' })))\n await rejects401(handler.readStateFromBearer(signScoped(nhiUser, {})))\n })\n\n it('a token signed by one set is not verified by the other', async () => {\n const handler = realHandler()\n // claims a scoped kid but is signed with the session private key\n await rejects401(handler.readStateFromBearer(signWith(sessionKeys.privateKey, 'scoped-sd-1', nhiUser, { typ: 'at+jwt' })))\n // and the other way round, on the cookie path\n await rejects401(handler.readStateFromCookie(toCookies(signWith(scopedKeys.privateKey, 'sd-1', testUser))))\n })\n\n it('cookie path refuses access tokens and enforces the scope/key-set pairing', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhiUser, { typ: 'at+jwt' }))))\n await rejects401(handler.readStateFromCookie(toCookies(signSession({ ...testUser, scope: 'datasets' }))))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(testUser))))\n assert.deepEqual((await handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })))).scopes, ['datasets'])\n assert.equal((await handler.readStateFromCookie(toCookies(signSession(testUser)))).scopes, undefined)\n })\n\n it('applies the same kind rules when only decoding', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhiUser, { typ: 'at+jwt' })), true))\n await rejects401(handler.readStateFromCookie(toCookies(signSession({ ...testUser, scope: 'datasets' })), true))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(testUser)), true))\n assert.equal((await handler.readStateFromCookie(toCookies(signSession(testUser)), true)).user?.id, 'user1')\n })\n\n it('scoped-set tokens cannot carry superadmin claims and must expire', async () => {\n const handler = realHandler()\n for (const claims of [{ adminMode: 1 }, { asAdmin: { id: 'a', name: 'A' } }, { isAdmin: 1 }, { isAdmin: 0 }, { adminMode: false }]) {\n await rejects401(handler.readStateFromBearer(signScoped({ ...nhiUser, ...claims }, { typ: 'at+jwt' })))\n }\n // no exp claim at all\n const noExp = jwt.sign(nhiUser, scopedKeys.privateKey, { algorithm: 'RS256', keyid: 'scoped-sd-1', header: { alg: 'RS256', typ: 'at+jwt' }, noTimestamp: true })\n assert.equal((jwt.decode(noExp) as any).exp, undefined)\n await rejects401(handler.readStateFromBearer(noExp))\n })\n\n it('verifyToken only accepts the session key set', async () => {\n const handler = realHandler()\n assert.equal((await handler.verifyToken(signSession({ id: 'user1' }))).id, 'user1')\n await assert.rejects(handler.verifyToken(signScoped({ id: 'user1', scope: 'datasets' })))\n })\n\n it('only RS256 is accepted', async () => {\n const handler = realHandler()\n const token = jwt.sign(testUser, sessionKeys.privateKey, { algorithm: 'PS256', keyid: 'sd-1', expiresIn: '5m' })\n await rejects401(handler.readStateFromCookie(toCookies(token)))\n await assert.rejects(handler.verifyToken(token))\n })\n})\n\ndescribe('verification errors', () => {\n const stubbed = (err: Error) => {\n const handler = realHandler()\n ;(handler as any).getSigningKeyObject = async () => { throw err }\n return handler\n }\n const named = (name: string, message = name) => Object.assign(new Error(message), { name })\n const token = () => signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' }, { typ: 'at+jwt' })\n\n it('infrastructure failures are a 500 and never clear the cookies', async () => {\n for (const err of [named('JwksRateLimitError'), new Error('connect ECONNREFUSED'), named('JwksError')]) {\n const handler = stubbed(err)\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })), false, () => { cleared = true }),\n (e: any) => e.status === 500\n )\n assert.equal(cleared, false)\n await assert.rejects(handler.readStateFromBearer(token()), (e: any) => e.status === 500)\n }\n })\n\n it('a kid absent from the jwks is a 401 and clears the cookies', async () => {\n const handler = stubbed(named('SigningKeyNotFoundError'))\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })), false, () => { cleared = true }),\n (e: any) => e.status === 401\n )\n assert.equal(cleared, true)\n await rejects401(handler.readStateFromBearer(token()))\n })\n\n it('looks a scoped kid up in the scoped client and another kid in the session client', async () => {\n const handler = new SessionHandler()\n const calls: string[] = []\n const client = (name: string, key: any) => ({ getSigningKey: async (kid: string) => { calls.push(name + ':' + kid); return { getPublicKey: () => key.export({ format: 'pem', type: 'spki' }) } } })\n ;(handler as any)._jwksClients = { session: client('session', sessionKeys.publicKey), scoped: client('scoped', scopedKeys.publicKey) }\n await handler.verifyTokenKeySet(signSession({ id: 'u' }))\n await handler.verifyTokenKeySet(signScoped({ id: 'u' }))\n assert.deepEqual(calls, ['session:sd-1', 'scoped:scoped-sd-1'])\n })\n})\n\ndescribe('malformed tokens, typ normalization, one organization per scoped cookie token', () => {\n const b64 = (o: any) => Buffer.from(typeof o === 'string' ? o : JSON.stringify(o)).toString('base64url')\n const malformed = (typ: string, kid: string) => b64({ alg: 'RS256', typ, kid }) + '.' + b64('{not json') + '.sig'\n const oneOrg = [testUser.organizations[0]]\n\n it('a token whose payload is not JSON is a 401 and clears the cookies', async () => {\n for (const onlyDecode of [false, true]) {\n const handler = realHandler()\n let cleared = false\n await rejects401(handler.readStateFromCookie(toCookies(malformed('JWT', 'sd-1')), onlyDecode, () => { cleared = true }))\n assert.equal(cleared, true)\n }\n })\n\n it('a bearer whose payload is not JSON is a 401', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromBearer(malformed('JWT', 'sd-1')))\n await rejects401(handler.readStateFromBearer(malformed('at+jwt', 'scoped-sd-1')))\n })\n\n it('a scoped cookie token carrying more than one organization is a 401', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromCookie(toCookies(signScoped({ ...testUser, scope: 'datasets' }))))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped({ ...testUser, scope: 'datasets' })), true))\n assert.equal((await handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: oneOrg, scope: 'datasets' })))).organization, undefined)\n })\n\n it('typ at+jwt is compared case-insensitively with an optional application/ prefix', async () => {\n const handler = realHandler()\n const nhi = { ...testUser, organizations: oneOrg, scope: 'datasets:read' }\n for (const typ of ['AT+JWT', 'application/at+jwt', 'Application/AT+JWT']) {\n assert.equal((await handler.readStateFromBearer(signScoped(nhi, { typ }))).user?.id, 'user1')\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhi, { typ }))))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhi, { typ })), true))\n }\n })\n})\n"]}
1
+ {"version":3,"file":"session.test.js","sourceRoot":"","sources":["session.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from 'node:test'\nimport { strict as assert } from 'assert'\nimport { generateKeyPairSync } from 'node:crypto'\nimport jwt from 'jsonwebtoken'\nimport { SessionHandler, keySetOfKid } from './session.js'\n\nconst testUser = {\n email: 'test@example.com',\n id: 'user1',\n name: 'Test User',\n organizations: [\n { id: 'org1', name: 'Test Org', role: 'admin' },\n { id: 'org2', name: 'Other Org', role: 'user', department: 'dep1', departmentName: 'Department 1' }\n ]\n}\n\n// a handler whose token verification is stubbed, counts the number of verifications\nconst stubbedHandler = () => {\n const handler = new SessionHandler()\n const calls = { count: 0 }\n handler.verifyTokenKeySet = async () => {\n calls.count++\n return { payload: structuredClone(testUser), header: { alg: 'RS256' }, keySet: 'session' }\n }\n return { handler, calls }\n}\n\ndescribe('SessionHandler.readStateFromCookie', () => {\n it('should return default session with no cookies', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie(undefined)\n assert.equal(state.lang, 'fr')\n assert.equal(state.user, undefined)\n assert.equal(state.dark, undefined)\n })\n\n it('should parse dark mode cookie', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('theme_dark=true')\n assert.equal(state.dark, true)\n })\n\n it('should parse i18n_lang cookie', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('i18n_lang=en')\n assert.equal(state.lang, 'en')\n })\n\n it('should use defaultLang when no i18n_lang cookie', async () => {\n const handler = new SessionHandler()\n handler.defaultLang = 'de'\n const state = await handler.readStateFromCookie('')\n assert.equal(state.lang, 'de')\n })\n\n it('should return early when no id_token', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('i18n_lang=en; theme_dark=1')\n assert.equal(state.lang, 'en')\n assert.equal(state.dark, true)\n assert.equal(state.user, undefined)\n })\n\n it('should return early when no id_token_sign and not onlyDecode', async () => {\n const handler = new SessionHandler()\n const state = await handler.readStateFromCookie('id_token=header.payload')\n assert.equal(state.user, undefined)\n })\n\n it('should select the organization account from cookies', async () => {\n const { handler } = stubbedHandler()\n const state = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s; id_token_org=org2; id_token_dep=dep1')\n assert.equal(state.account?.type, 'organization')\n assert.equal(state.account?.id, 'org2')\n assert.equal(state.account?.department, 'dep1')\n assert.equal(state.accountRole, 'user')\n })\n\n it('should build a user account when no org cookie', async () => {\n const { handler } = stubbedHandler()\n const state = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s')\n assert.equal(state.account?.type, 'user')\n assert.equal(state.account?.id, 'user1')\n assert.equal(state.accountRole, 'admin')\n })\n\n it('should accept an already-parsed cookies object without re-parsing', async () => {\n const { handler, calls } = stubbedHandler()\n const parsed = { id_token: 'h.p', id_token_sign: 's', id_token_org: 'org2', id_token_dep: 'dep1', i18n_lang: 'en', theme_dark: '1' }\n const state = await handler.readStateFromCookie(parsed)\n assert.equal(state.lang, 'en')\n assert.equal(state.dark, true)\n assert.equal(state.account?.type, 'organization')\n assert.equal(state.account?.id, 'org2')\n assert.equal(calls.count, 1)\n })\n\n it('should produce equivalent state from string and object cookie inputs', async () => {\n const { handler } = stubbedHandler()\n const fromString = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s; i18n_lang=en')\n const fromObject = await handler.readStateFromCookie({ id_token: 'h.p', id_token_sign: 's', i18n_lang: 'en' })\n assert.deepEqual(fromString, fromObject)\n })\n\n it('should verify the token on every request', async () => {\n const { handler, calls } = stubbedHandler()\n const cookies = 'id_token=h.p; id_token_sign=s'\n await handler.readStateFromCookie(cookies)\n await handler.readStateFromCookie(cookies)\n assert.equal(calls.count, 2)\n })\n\n it('should return a mutable state that callers can adjust', async () => {\n const { handler } = stubbedHandler()\n const state = await handler.readStateFromCookie('id_token=h.p; id_token_sign=s')\n state.user!.name = 'mutated' // must not throw\n state.user!.organizations[0].role = 'other'\n assert.equal(state.user!.name, 'mutated')\n assert.equal(state.user!.organizations[0].role, 'other')\n })\n\n it('should decode without verifying in onlyDecode mode', async () => {\n const handler = new SessionHandler()\n const token = jwt.sign(structuredClone(testUser), 'secret')\n const [header, payload, sign] = token.split('.')\n const state = await handler.readStateFromCookie(`id_token=${header}.${payload}; id_token_sign=${sign}`, true)\n assert.equal(state.user?.id, 'user1')\n assert.equal(state.account?.type, 'user')\n })\n\n it('should reject and clear the session when the token is invalid', async () => {\n const handler = new SessionHandler()\n handler.verifyTokenKeySet = async () => { throw Object.assign(new Error('jwt expired'), { name: 'TokenExpiredError' }) }\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie('id_token=h.p; id_token_sign=s', undefined, () => { cleared = true }),\n (err: any) => err.status === 401\n )\n assert.equal(cleared, true)\n })\n\n it('should surface a 500 without clearing the session on a jwks error', async () => {\n const handler = new SessionHandler()\n handler.verifyTokenKeySet = async () => { throw Object.assign(new Error('jwks down'), { name: 'JwksError' }) }\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie('id_token=h.p; id_token_sign=s', undefined, () => { cleared = true }),\n (err: any) => err.status === 500\n )\n assert.equal(cleared, false)\n })\n})\n\ndescribe('SessionHandler.verifyToken', () => {\n it('should consult the JWKS on every verification but cache the parsed key per kid', async () => {\n const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })\n const pem = publicKey.export({ format: 'pem', type: 'spki' }) as string\n const handler = new SessionHandler()\n let getSigningKeyCalls = 0\n let getPublicKeyCalls = 0\n ;(handler as any)._jwksClients = {\n session: {\n getSigningKey: async () => {\n getSigningKeyCalls++\n return { getPublicKey: () => { getPublicKeyCalls++; return pem } }\n }\n }\n }\n const token = jwt.sign({ id: 'user1' }, privateKey, { algorithm: 'RS256', keyid: 'kid1' })\n const payload1 = await handler.verifyToken(token)\n assert.equal(payload1.id, 'user1')\n const payload2 = await handler.verifyToken(token)\n assert.equal(payload2.id, 'user1')\n // the signing key must be re-checked against the JWKS every time so that a key\n // rotated out of the keyset stops being accepted (lib#41 regression)\n assert.equal(getSigningKeyCalls, 2)\n // but the expensive PEM export + parsing is cached per kid\n assert.equal(getPublicKeyCalls, 1)\n })\n\n it('should reject a token whose signing key was rotated out of the JWKS', async () => {\n const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })\n const pem = publicKey.export({ format: 'pem', type: 'spki' }) as string\n const handler = new SessionHandler()\n let keyPresent = true\n ;(handler as any)._jwksClients = {\n session: {\n getSigningKey: async () => {\n if (!keyPresent) throw Object.assign(new Error('key not found'), { name: 'SigningKeyNotFoundError' })\n return { getPublicKey: () => pem }\n }\n }\n }\n const token = jwt.sign({ id: 'user1' }, privateKey, { algorithm: 'RS256', keyid: 'kid1' })\n // first verification succeeds and would populate any key cache\n assert.equal((await handler.verifyToken(token)).id, 'user1')\n // the key is rotated out of the published keyset\n keyPresent = false\n // a cached parsed key must not let the rotated-out key keep verifying\n await assert.rejects(handler.verifyToken(token))\n })\n})\n\nconst sessionKeys = generateKeyPairSync('rsa', { modulusLength: 2048 })\nconst scopedKeys = generateKeyPairSync('rsa', { modulusLength: 2048 })\nconst signWith = (privateKey: any, kid: string, payload: any, header: any = {}, opts: any = {}) =>\n jwt.sign(payload, privateKey, { algorithm: 'RS256', expiresIn: '5m', keyid: kid, header: { alg: 'RS256', ...header }, ...opts })\nconst signSession = (payload: any, header?: any, opts?: any) => signWith(sessionKeys.privateKey, 'sd-1', payload, header, opts)\nconst signScoped = (payload: any, header?: any, opts?: any) => signWith(scopedKeys.privateKey, 'scoped-sd-1', payload, header, opts)\n// real jwt verification against two local key pairs, one per key set\nconst realHandler = (scopeAware = true, siteAudiences: string[] | null = ['https://x']) => {\n const handler = new SessionHandler()\n handler.scopeAware = scopeAware\n // the tests read bearers as if on the site https://x unless told otherwise\n const readStateFromBearer = handler.readStateFromBearer.bind(handler)\n handler.readStateFromBearer = (token: string, lang?: string) => readStateFromBearer(token, lang, siteAudiences ?? undefined)\n ;(handler as any).getSigningKeyObject = async (keySet: string) => keySet === 'scoped' ? scopedKeys.publicKey : sessionKeys.publicKey\n return handler\n}\nconst toCookies = (token: string) => {\n const [h, p, s] = token.split('.')\n return `id_token=${h}.${p}; id_token_sign=${s}`\n}\nconst rejects401 = (p: Promise<any>) => assert.rejects(p, (err: any) => err.status === 401)\n\ndescribe('SessionHandler.readStateFromBearer', () => {\n const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read organizations:read', iss: 'https://x/simple-directory', aud: 'https://x', client_id: 'nhi-a', jti: 'j1' }\n const bearer = (payload: any, header: any = { typ: 'at+jwt' }, opts?: any) => signScoped(payload, header, opts)\n\n it('reads an at+jwt access token, bound to its single organization, with its scopes', async () => {\n const state = await realHandler().readStateFromBearer(bearer(nhiUser))\n assert.equal(state.user?.id, 'user1')\n assert.equal(state.account?.type, 'organization')\n assert.equal(state.account?.id, 'org1')\n assert.equal(state.accountRole, 'admin')\n assert.deepEqual(state.scopes, ['datasets:read', 'organizations:read'])\n })\n\n it('refuses (401) a token bound to several organizations', async () => {\n await rejects401(realHandler().readStateFromBearer(bearer(testUser)))\n })\n\n it('gives a personal account session to a token with no organization membership', async () => {\n const state = await realHandler().readStateFromBearer(bearer({ ...testUser, organizations: [], scope: 'datasets', aud: 'https://x' }))\n assert.equal(state.account?.type, 'user')\n assert.equal(state.account?.id, 'user1')\n assert.equal(state.accountRole, 'admin')\n })\n\n it('refuses a token without an organizations key', async () => {\n const { organizations, ...noOrgs } = testUser\n await rejects401(realHandler().readStateFromBearer(bearer({ ...noOrgs, scope: 'datasets', aud: 'https://x' })))\n })\n\n it('refuses (401) an invalid scope claim on the bearer and cookie paths', async () => {\n for (const scope of [null, 12, ['a']]) {\n await rejects401(realHandler().readStateFromBearer(bearer({ ...nhiUser, scope })))\n const handler = new SessionHandler()\n handler.scopeAware = true\n handler.verifyTokenKeySet = async () => ({ payload: structuredClone({ ...testUser, scope, exp: 4102444800 }), header: { alg: 'RS256' }, keySet: 'scoped' })\n await rejects401(handler.readStateFromCookie('id_token=a.b; id_token_sign=c'))\n }\n })\n\n it('refuses a scoped session unless the handler is scope-aware', async () => {\n const handler = realHandler(false)\n assert.equal(handler.scopeAware, false)\n await assert.rejects(handler.readStateFromBearer(bearer(nhiUser)), (err: any) => err.status === 403)\n const cookies = toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' }))\n await assert.rejects(handler.readStateFromCookie(cookies), (err: any) => err.status === 403)\n handler.scopeAware = true\n assert.deepEqual((await handler.readStateFromCookie(cookies)).scopes, ['datasets'])\n assert.deepEqual((await handler.readStateFromBearer(bearer(nhiUser))).scopes, ['datasets:read', 'organizations:read'])\n })\n\n it('refuses an expired token or one signed by another key when really verifying', async () => {\n const { privateKey: otherKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })\n const handler = realHandler()\n assert.equal((await handler.readStateFromBearer(bearer(nhiUser))).user?.id, 'user1')\n await rejects401(handler.readStateFromBearer(bearer(nhiUser, undefined, { expiresIn: -10 })))\n await rejects401(handler.readStateFromBearer(signWith(otherKey, 'scoped-sd-1', nhiUser, { typ: 'at+jwt' })))\n })\n\n it('refuses a session token or an exchange token presented as bearer', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromBearer(signSession(testUser)))\n await rejects401(handler.readStateFromBearer(signSession({ user: 'user1', session: 'abc' }, { typ: 'JWT' })))\n await rejects401(handler.readStateFromBearer('not-a-jwt'))\n })\n\n it('bearer with matching aud string is accepted and claims move to accessToken', async () => {\n const state = await realHandler().readStateFromBearer(bearer(nhiUser))\n assert.deepEqual(state.accessToken, { issuer: 'https://x/simple-directory', clientId: 'nhi-a', audience: ['https://x'] })\n for (const claim of ['iss', 'sub', 'aud', 'client_id', 'jti', 'nbf']) assert.equal(claim in (state.user as object), false, claim)\n })\n\n it('bearer with aud array containing the site is accepted', async () => {\n const state = await realHandler().readStateFromBearer(bearer({ ...nhiUser, aud: ['https://other', 'https://x'] }))\n assert.deepEqual(state.accessToken?.audience, ['https://other', 'https://x'])\n })\n\n it('bearer without aud is refused 401', async () => {\n const { aud, ...noAud } = nhiUser\n await rejects401(realHandler().readStateFromBearer(bearer(noAud)))\n })\n\n it('bearer with another aud is refused 401', async () => {\n await rejects401(realHandler().readStateFromBearer(bearer({ ...nhiUser, aud: 'https://y' })))\n await rejects401(realHandler().readStateFromBearer(bearer({ ...nhiUser, aud: ['https://y', 'https://z'] })))\n })\n\n it('bearer refused 401 when audiences are not provided', async () => {\n await rejects401(realHandler(true, null).readStateFromBearer(bearer(nhiUser)))\n })\n\n it('fills scopes from a cookie session too', async () => {\n const state = await realHandler().readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })))\n assert.deepEqual(state.scopes, ['datasets'])\n })\n})\n\ndescribe('key sets and token kinds', () => {\n const nhiUser = { ...testUser, organizations: [testUser.organizations[0]], nhi: 1, scope: 'datasets:read', aud: 'https://x' }\n\n it('keySetOfKid', () => {\n assert.equal(keySetOfKid('scoped-sd-1'), 'scoped')\n assert.equal(keySetOfKid('sd-1'), 'session')\n assert.equal(keySetOfKid(undefined), 'session')\n })\n\n it('bearer requires an at+jwt from the scoped set', async () => {\n const handler = realHandler()\n assert.equal((await handler.readStateFromBearer(signScoped(nhiUser, { typ: 'at+jwt' }))).user?.id, 'user1')\n await rejects401(handler.readStateFromBearer(signSession(nhiUser, { typ: 'at+jwt' })))\n await rejects401(handler.readStateFromBearer(signScoped(nhiUser, {})))\n })\n\n it('a token signed by one set is not verified by the other', async () => {\n const handler = realHandler()\n // claims a scoped kid but is signed with the session private key\n await rejects401(handler.readStateFromBearer(signWith(sessionKeys.privateKey, 'scoped-sd-1', nhiUser, { typ: 'at+jwt' })))\n // and the other way round, on the cookie path\n await rejects401(handler.readStateFromCookie(toCookies(signWith(scopedKeys.privateKey, 'sd-1', testUser))))\n })\n\n it('cookie path refuses access tokens and enforces the scope/key-set pairing', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhiUser, { typ: 'at+jwt' }))))\n await rejects401(handler.readStateFromCookie(toCookies(signSession({ ...testUser, scope: 'datasets' }))))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(testUser))))\n assert.deepEqual((await handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })))).scopes, ['datasets'])\n assert.equal((await handler.readStateFromCookie(toCookies(signSession(testUser)))).scopes, undefined)\n })\n\n it('applies the same kind rules when only decoding', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhiUser, { typ: 'at+jwt' })), true))\n await rejects401(handler.readStateFromCookie(toCookies(signSession({ ...testUser, scope: 'datasets' })), true))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(testUser)), true))\n assert.equal((await handler.readStateFromCookie(toCookies(signSession(testUser)), true)).user?.id, 'user1')\n })\n\n it('scoped-set tokens cannot carry superadmin claims and must expire', async () => {\n const handler = realHandler()\n for (const claims of [{ adminMode: 1 }, { asAdmin: { id: 'a', name: 'A' } }, { isAdmin: 1 }, { isAdmin: 0 }, { adminMode: false }]) {\n await rejects401(handler.readStateFromBearer(signScoped({ ...nhiUser, ...claims }, { typ: 'at+jwt' })))\n }\n // no exp claim at all\n const noExp = jwt.sign(nhiUser, scopedKeys.privateKey, { algorithm: 'RS256', keyid: 'scoped-sd-1', header: { alg: 'RS256', typ: 'at+jwt' }, noTimestamp: true })\n assert.equal((jwt.decode(noExp) as any).exp, undefined)\n await rejects401(handler.readStateFromBearer(noExp))\n })\n\n it('verifyToken only accepts the session key set', async () => {\n const handler = realHandler()\n assert.equal((await handler.verifyToken(signSession({ id: 'user1' }))).id, 'user1')\n await assert.rejects(handler.verifyToken(signScoped({ id: 'user1', scope: 'datasets' })))\n })\n\n it('only RS256 is accepted', async () => {\n const handler = realHandler()\n const token = jwt.sign(testUser, sessionKeys.privateKey, { algorithm: 'PS256', keyid: 'sd-1', expiresIn: '5m' })\n await rejects401(handler.readStateFromCookie(toCookies(token)))\n await assert.rejects(handler.verifyToken(token))\n })\n})\n\ndescribe('verification errors', () => {\n const stubbed = (err: Error) => {\n const handler = realHandler()\n ;(handler as any).getSigningKeyObject = async () => { throw err }\n return handler\n }\n const named = (name: string, message = name) => Object.assign(new Error(message), { name })\n const token = () => signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' }, { typ: 'at+jwt' })\n\n it('infrastructure failures are a 500 and never clear the cookies', async () => {\n for (const err of [named('JwksRateLimitError'), new Error('connect ECONNREFUSED'), named('JwksError')]) {\n const handler = stubbed(err)\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })), false, () => { cleared = true }),\n (e: any) => e.status === 500\n )\n assert.equal(cleared, false)\n await assert.rejects(handler.readStateFromBearer(token()), (e: any) => e.status === 500)\n }\n })\n\n it('a kid absent from the jwks is a 401 and clears the cookies', async () => {\n const handler = stubbed(named('SigningKeyNotFoundError'))\n let cleared = false\n await assert.rejects(\n handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: [testUser.organizations[0]], scope: 'datasets' })), false, () => { cleared = true }),\n (e: any) => e.status === 401\n )\n assert.equal(cleared, true)\n await rejects401(handler.readStateFromBearer(token()))\n })\n\n it('looks a scoped kid up in the scoped client and another kid in the session client', async () => {\n const handler = new SessionHandler()\n const calls: string[] = []\n const client = (name: string, key: any) => ({ getSigningKey: async (kid: string) => { calls.push(name + ':' + kid); return { getPublicKey: () => key.export({ format: 'pem', type: 'spki' }) } } })\n ;(handler as any)._jwksClients = { session: client('session', sessionKeys.publicKey), scoped: client('scoped', scopedKeys.publicKey) }\n await handler.verifyTokenKeySet(signSession({ id: 'u' }))\n await handler.verifyTokenKeySet(signScoped({ id: 'u' }))\n assert.deepEqual(calls, ['session:sd-1', 'scoped:scoped-sd-1'])\n })\n})\n\ndescribe('malformed tokens, typ normalization, one organization per scoped cookie token', () => {\n const b64 = (o: any) => Buffer.from(typeof o === 'string' ? o : JSON.stringify(o)).toString('base64url')\n const malformed = (typ: string, kid: string) => b64({ alg: 'RS256', typ, kid }) + '.' + b64('{not json') + '.sig'\n const oneOrg = [testUser.organizations[0]]\n\n it('a token whose payload is not JSON is a 401 and clears the cookies', async () => {\n for (const onlyDecode of [false, true]) {\n const handler = realHandler()\n let cleared = false\n await rejects401(handler.readStateFromCookie(toCookies(malformed('JWT', 'sd-1')), onlyDecode, () => { cleared = true }))\n assert.equal(cleared, true)\n }\n })\n\n it('a bearer whose payload is not JSON is a 401', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromBearer(malformed('JWT', 'sd-1')))\n await rejects401(handler.readStateFromBearer(malformed('at+jwt', 'scoped-sd-1')))\n })\n\n it('a scoped cookie token carrying more than one organization is a 401', async () => {\n const handler = realHandler()\n await rejects401(handler.readStateFromCookie(toCookies(signScoped({ ...testUser, scope: 'datasets' }))))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped({ ...testUser, scope: 'datasets' })), true))\n assert.equal((await handler.readStateFromCookie(toCookies(signScoped({ ...testUser, organizations: oneOrg, scope: 'datasets' })))).organization, undefined)\n })\n\n it('typ at+jwt is compared case-insensitively with an optional application/ prefix', async () => {\n const handler = realHandler()\n const nhi = { ...testUser, organizations: oneOrg, scope: 'datasets:read', aud: 'https://x' }\n for (const typ of ['AT+JWT', 'application/at+jwt', 'Application/AT+JWT']) {\n assert.equal((await handler.readStateFromBearer(signScoped(nhi, { typ }))).user?.id, 'user1')\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhi, { typ }))))\n await rejects401(handler.readStateFromCookie(toCookies(signScoped(nhi, { typ })), true))\n }\n })\n})\n"]}