@data-fair/lib-node 2.13.1 → 2.13.3
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/axios.js +7 -1
- package/axios.js.map +1 -1
- package/events-queue.js +1 -1
- package/events-queue.js.map +1 -1
- package/package.json +1 -1
- package/session.js +16 -13
- package/session.js.map +1 -1
- package/session.test.js +28 -3
- package/session.test.js.map +1 -1
package/axios.js
CHANGED
|
@@ -23,7 +23,13 @@ export function axiosBuilder(opts = {}, beforeInterceptors) {
|
|
|
23
23
|
const ax = axios.create({
|
|
24
24
|
httpAgent,
|
|
25
25
|
httpsAgent,
|
|
26
|
-
...opts
|
|
26
|
+
...opts,
|
|
27
|
+
headers: {
|
|
28
|
+
// identifies requests coming from the data-fair stack, with a contact URL as
|
|
29
|
+
// recommended/required by policies like https://foundation.wikimedia.org/wiki/Policy:Wikimedia_Foundation_User-Agent_Policy
|
|
30
|
+
'User-Agent': 'Data-Fair (https://github.com/data-fair)',
|
|
31
|
+
...opts.headers
|
|
32
|
+
}
|
|
27
33
|
});
|
|
28
34
|
if (beforeInterceptors)
|
|
29
35
|
beforeInterceptors(ax);
|
package/axios.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axios.js","sourceRoot":"","sources":["axios.ts"],"names":[],"mappings":"AAAA,wGAAwG;AAGxG,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAExD,MAAM,EAAE,eAAe,EAAE,GAAG,KAAK,CAAA;AAEjC,MAAM,iBAAkB,SAAQ,KAAK;IACnC;QACE,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF;AAED,MAAM,kBAAkB,GAAG,CAAC,MAAkC,EAAE,EAAE;IAChE,sBAAsB;IACtB,wEAAwE;IACxE,KAAK,CAAC,eAAe,GAAG,CAAC,CAAA;IACzB,MAAM,YAAY,GAAG,IAAI,iBAAiB,EAAE,CAAA;IAC5C,KAAK,CAAC,eAAe,GAAG,eAAe,CAAA;IACvC,KAAK,CAAC,iBAAiB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAA;IACzD,aAAa;IACb,MAAM,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,UAAU,YAAY,CAAE,OAA4B,EAAE,EAAE,kBAAgD;IAC5G,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;QACtB,SAAS;QACT,UAAU;QACV,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"axios.js","sourceRoot":"","sources":["axios.ts"],"names":[],"mappings":"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","sourcesContent":["// prepare an axios instance with improved error management and some performance tuning for nodejs usage\n\nimport type { InternalAxiosRequestConfig, AxiosInstance, CreateAxiosDefaults } from 'axios'\nimport axios from 'axios'\nimport { httpAgent, httpsAgent } from './http-agents.js'\n\nconst { stackTraceLimit } = Error\n\nclass AxiosRequestError extends Error {\n constructor () {\n super()\n this.name = 'AxiosRequestError'\n }\n}\n\nconst requestInterceptor = (config: InternalAxiosRequestConfig) => {\n // better stack traces\n // see https://github.com/axios/axios/issues/2387#issuecomment-652242713\n Error.stackTraceLimit = 0\n const errorContext = new AxiosRequestError()\n Error.stackTraceLimit = stackTraceLimit\n Error.captureStackTrace(errorContext, requestInterceptor)\n // @ts-ignore\n config.errorContext = errorContext\n return config\n}\n\nexport function axiosBuilder (opts: CreateAxiosDefaults = {}, beforeInterceptors?: (ax: AxiosInstance) => void): AxiosInstance {\n const ax = axios.create({\n httpAgent,\n httpsAgent,\n ...opts,\n headers: {\n // identifies requests coming from the data-fair stack, with a contact URL as\n // recommended/required by policies like https://foundation.wikimedia.org/wiki/Policy:Wikimedia_Foundation_User-Agent_Policy\n 'User-Agent': 'Data-Fair (https://github.com/data-fair)',\n ...opts.headers\n }\n })\n\n if (beforeInterceptors) beforeInterceptors(ax)\n\n ax.interceptors.request.use(requestInterceptor)\n\n // shorter stack traces\n ax.interceptors.response.use(response => response, error => {\n if (!error.response) {\n if (error.config?.errorContext?.stack) error.stack += '\\nRequest context:\\n' + error.config?.errorContext?.stack\n return Promise.reject(error)\n }\n\n delete error.response.request\n error.response.config = { method: error.response.config.method, url: error.response.config.url, data: error.response.config.data }\n if (error.response.config.data && error.response.config.data._writableState) delete error.response.config.data\n // destroy() a streamed error body before dropping it, else its socket leaks\n if (error.response.data && error.response.data._readableState) { error.response.data.destroy(); delete error.response.data }\n let messageText = error.response.statusText\n if (error.response.data) {\n messageText = typeof error.response.data === 'string' ? error.response.data : JSON.stringify(error.response.data)\n }\n error.response.message = `${error.response.status} - ${messageText}`\n Object.assign(error.config.errorContext, error.response)\n return Promise.reject(error.config.errorContext)\n })\n return ax\n}\n\nexport const axiosInstance = axiosBuilder()\n\nexport default axiosInstance\n"]}
|
package/events-queue.js
CHANGED
|
@@ -60,7 +60,7 @@ export class EventsQueue {
|
|
|
60
60
|
internalError('events-queue-push-notif', err);
|
|
61
61
|
// retry later
|
|
62
62
|
if (err.status >= 500 && (notifications.length + this.notificationsQueue.length) < 1000) {
|
|
63
|
-
this.notificationsQueue.unshift();
|
|
63
|
+
this.notificationsQueue.unshift(notification);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
package/events-queue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-queue.js","sourceRoot":"","sources":["events-queue.ts"],"names":[],"mappings":"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
|
|
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'\nimport 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"]}
|
package/package.json
CHANGED
package/session.js
CHANGED
|
@@ -13,24 +13,27 @@ export class SessionHandler {
|
|
|
13
13
|
}
|
|
14
14
|
initJWKS(directoryUrl = 'http://simple-directory:8080') {
|
|
15
15
|
this._jwksClient = JwksClient({ jwksUri: directoryUrl + '/.well-known/jwks.json' });
|
|
16
|
+
// the parsed key cache is derived from this jwks client (which enforces key rotation
|
|
17
|
+
// through its own ttl), so it must be dropped whenever the client is recreated
|
|
18
|
+
this._signingKeyObjects.clear();
|
|
16
19
|
}
|
|
17
20
|
// jwks-rsa caches the JWK per kid but it exports a new PEM string at every access
|
|
18
|
-
// and jsonwebtoken parses PEM
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
+
// and jsonwebtoken parses that PEM again at every verification: caching the parsed
|
|
22
|
+
// KeyObject per kid skips both costs.
|
|
23
|
+
// IMPORTANT: getSigningKey must still be awaited on every verification. It is the only
|
|
24
|
+
// thing that re-checks the kid against the current keyset, so a key rotated out of the
|
|
25
|
+
// JWKS stops being accepted. Short-circuiting it from a persistent cache would accept a
|
|
26
|
+
// rotated-out key forever (the lib#41 regression).
|
|
21
27
|
_signingKeyObjects = new Map();
|
|
22
|
-
getSigningKeyObject(kid) {
|
|
28
|
+
async getSigningKeyObject(kid) {
|
|
29
|
+
const signingKey = await this.jwksClient.getSigningKey(kid);
|
|
23
30
|
const cacheKey = kid ?? '';
|
|
24
|
-
let
|
|
25
|
-
if (!
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return createPublicKey(signingKey.getPublicKey());
|
|
29
|
-
})();
|
|
30
|
-
promise.catch(() => this._signingKeyObjects.delete(cacheKey));
|
|
31
|
-
this._signingKeyObjects.set(cacheKey, promise);
|
|
31
|
+
let keyObject = this._signingKeyObjects.get(cacheKey);
|
|
32
|
+
if (!keyObject) {
|
|
33
|
+
keyObject = createPublicKey(signingKey.getPublicKey());
|
|
34
|
+
this._signingKeyObjects.set(cacheKey, keyObject);
|
|
32
35
|
}
|
|
33
|
-
return
|
|
36
|
+
return keyObject;
|
|
34
37
|
}
|
|
35
38
|
// used for the main session token, but can also be used to use the jwks to verify other types of tokens
|
|
36
39
|
async verifyToken(token) {
|
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 { createPublicKey, type KeyObject } from 'node:crypto'\nimport jwt from 'jsonwebtoken'\nimport JwksClient from 'jwks-rsa'\nimport cookie from 'cookie'\nimport { httpError } from '@data-fair/lib-utils/http-errors.js'\n\ntype TokenSessionState = Omit<SessionState, 'lang' | 'dark'>\n\nexport class SessionHandler {\n private _jwksClient: JwksClient.JwksClient | undefined\n public defaultLang: string = 'fr'\n\n get jwksClient () {\n if (!this._jwksClient) throw new Error('session management was not initialized')\n return this._jwksClient\n }\n\n initJWKS (directoryUrl = 'http://simple-directory:8080') {\n this._jwksClient = JwksClient({ jwksUri: directoryUrl + '/.well-known/jwks.json' })\n }\n\n // jwks-rsa caches the JWK per kid but it exports a new PEM string at every access\n // and jsonwebtoken parses PEM strings again at every verification\n // caching the parsed KeyObject per kid skips both costs\n // the cache holds the pending promise so concurrent calls dedupe, and drops it on failure\n private _signingKeyObjects = new Map<string, Promise<KeyObject>>()\n private getSigningKeyObject (kid?: string): Promise<KeyObject> {\n const cacheKey = kid ?? ''\n let promise = this._signingKeyObjects.get(cacheKey)\n if (!promise) {\n promise = (async () => {\n const signingKey = await this.jwksClient.getSigningKey(kid)\n return createPublicKey(signingKey.getPublicKey())\n })()\n promise.catch(() => this._signingKeyObjects.delete(cacheKey))\n this._signingKeyObjects.set(cacheKey, promise)\n }\n return promise\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 async verifyToken (token: string): Promise<any> {\n const decoded = jwt.decode(token, { complete: true })\n if (!decoded) throw new Error('failed to decode token')\n const signingKeyObject = await this.getSigningKeyObject(decoded.header.kid)\n return jwt.verify(token, signingKeyObject)\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\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 // 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 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 user = jwt.decode(token) as User\n } else {\n try {\n user = await this.verifyToken(token) as User\n } catch (err: any) {\n console.warn(err)\n if (err.name === 'JwksError') {\n // happens in case of temporary unavailability if SD\n // better not to disconnect user in this case\n throw httpError(500, 'Session token public keys not initialized')\n } else {\n if (clearBrokenSession) clearBrokenSession()\n throw httpError(401)\n }\n }\n }\n if (!user) return { lang, dark }\n return Object.assign({ lang, dark } as SessionState, this.buildTokenSessionState(user, organizationId, departmentId, switchedRole))\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 { createPublicKey, type KeyObject } from 'node:crypto'\nimport jwt from 'jsonwebtoken'\nimport JwksClient from 'jwks-rsa'\nimport cookie from 'cookie'\nimport { httpError } from '@data-fair/lib-utils/http-errors.js'\n\ntype TokenSessionState = Omit<SessionState, 'lang' | 'dark'>\n\nexport class SessionHandler {\n private _jwksClient: JwksClient.JwksClient | undefined\n public defaultLang: string = 'fr'\n\n get jwksClient () {\n if (!this._jwksClient) throw new Error('session management was not initialized')\n return this._jwksClient\n }\n\n initJWKS (directoryUrl = 'http://simple-directory:8080') {\n this._jwksClient = JwksClient({ jwksUri: directoryUrl + '/.well-known/jwks.json' })\n // the parsed key cache is derived from this jwks client (which enforces key rotation\n // through its own ttl), so it must be dropped whenever the client is 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 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 private async getSigningKeyObject (kid?: string): Promise<KeyObject> {\n const signingKey = await this.jwksClient.getSigningKey(kid)\n const cacheKey = 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 // used for the main session token, but can also be used to use the jwks to verify other types of tokens\n async verifyToken (token: string): Promise<any> {\n const decoded = jwt.decode(token, { complete: true })\n if (!decoded) throw new Error('failed to decode token')\n const signingKeyObject = await this.getSigningKeyObject(decoded.header.kid)\n return jwt.verify(token, signingKeyObject)\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\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 // 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 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 user = jwt.decode(token) as User\n } else {\n try {\n user = await this.verifyToken(token) as User\n } catch (err: any) {\n console.warn(err)\n if (err.name === 'JwksError') {\n // happens in case of temporary unavailability if SD\n // better not to disconnect user in this case\n throw httpError(500, 'Session token public keys not initialized')\n } else {\n if (clearBrokenSession) clearBrokenSession()\n throw httpError(401)\n }\n }\n }\n if (!user) return { lang, dark }\n return Object.assign({ lang, dark } as SessionState, this.buildTokenSessionState(user, organizationId, departmentId, switchedRole))\n }\n}\n"]}
|
package/session.test.js
CHANGED
|
@@ -128,15 +128,16 @@ describe('SessionHandler.readStateFromCookie', () => {
|
|
|
128
128
|
});
|
|
129
129
|
});
|
|
130
130
|
describe('SessionHandler.verifyToken', () => {
|
|
131
|
-
it('should
|
|
131
|
+
it('should consult the JWKS on every verification but cache the parsed key per kid', async () => {
|
|
132
132
|
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
133
133
|
const pem = publicKey.export({ format: 'pem', type: 'spki' });
|
|
134
134
|
const handler = new SessionHandler();
|
|
135
135
|
let getSigningKeyCalls = 0;
|
|
136
|
+
let getPublicKeyCalls = 0;
|
|
136
137
|
handler._jwksClient = {
|
|
137
138
|
getSigningKey: async () => {
|
|
138
139
|
getSigningKeyCalls++;
|
|
139
|
-
return { getPublicKey: () => pem };
|
|
140
|
+
return { getPublicKey: () => { getPublicKeyCalls++; return pem; } };
|
|
140
141
|
}
|
|
141
142
|
};
|
|
142
143
|
const token = jwt.sign({ id: 'user1' }, privateKey, { algorithm: 'RS256', keyid: 'kid1' });
|
|
@@ -144,7 +145,31 @@ describe('SessionHandler.verifyToken', () => {
|
|
|
144
145
|
assert.equal(payload1.id, 'user1');
|
|
145
146
|
const payload2 = await handler.verifyToken(token);
|
|
146
147
|
assert.equal(payload2.id, 'user1');
|
|
147
|
-
|
|
148
|
+
// the signing key must be re-checked against the JWKS every time so that a key
|
|
149
|
+
// rotated out of the keyset stops being accepted (lib#41 regression)
|
|
150
|
+
assert.equal(getSigningKeyCalls, 2);
|
|
151
|
+
// but the expensive PEM export + parsing is cached per kid
|
|
152
|
+
assert.equal(getPublicKeyCalls, 1);
|
|
153
|
+
});
|
|
154
|
+
it('should reject a token whose signing key was rotated out of the JWKS', async () => {
|
|
155
|
+
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
156
|
+
const pem = publicKey.export({ format: 'pem', type: 'spki' });
|
|
157
|
+
const handler = new SessionHandler();
|
|
158
|
+
let keyPresent = true;
|
|
159
|
+
handler._jwksClient = {
|
|
160
|
+
getSigningKey: async () => {
|
|
161
|
+
if (!keyPresent)
|
|
162
|
+
throw Object.assign(new Error('key not found'), { name: 'SigningKeyNotFoundError' });
|
|
163
|
+
return { getPublicKey: () => pem };
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const token = jwt.sign({ id: 'user1' }, privateKey, { algorithm: 'RS256', keyid: 'kid1' });
|
|
167
|
+
// first verification succeeds and would populate any key cache
|
|
168
|
+
assert.equal((await handler.verifyToken(token)).id, 'user1');
|
|
169
|
+
// the key is rotated out of the published keyset
|
|
170
|
+
keyPresent = false;
|
|
171
|
+
// a cached parsed key must not let the rotated-out key keep verifying
|
|
172
|
+
await assert.rejects(handler.verifyToken(token));
|
|
148
173
|
});
|
|
149
174
|
});
|
|
150
175
|
//# sourceMappingURL=session.test.js.map
|
package/session.test.js.map
CHANGED
|
@@ -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 } 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.verifyToken = async () => {\n calls.count++\n return structuredClone(testUser)\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.verifyToken = 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.verifyToken = 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 verify a token and cache the signing 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 ;(handler as any)._jwksClient = {\n getSigningKey: async () => {\n getSigningKeyCalls++\n return { getPublicKey: () => pem }\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 assert.equal(getSigningKeyCalls, 1)\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"session.test.js","sourceRoot":"","sources":["session.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,GAAG,MAAM,cAAc,CAAA;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,MAAM,QAAQ,GAAG;IACf,KAAK,EAAE,kBAAkB;IACzB,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,WAAW;IACjB,aAAa,EAAE;QACb,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE;QAC/C,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE;KACpG;CACF,CAAA;AAED,oFAAoF;AACpF,MAAM,cAAc,GAAG,GAAG,EAAE;IAC1B,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;IACpC,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;IAC1B,OAAO,CAAC,WAAW,GAAG,KAAK,IAAI,EAAE;QAC/B,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC,CAAA;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;AAC3B,CAAC,CAAA;AAED,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAA;QAC1D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QACnC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAA;QAClE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAA;QAC/D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAA;QAC1B,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAA;QACnD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,4BAA4B,CAAC,CAAA;QAC7E,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,yBAAyB,CAAC,CAAA;QAC1E,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,qEAAqE,CAAC,CAAA;QACtH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;QACjD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;QACvC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,+BAA+B,CAAC,CAAA;QAChF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;QACzC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QACxC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,cAAc,EAAE,CAAA;QAC3C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAA;QACpI,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAA;QACvD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;QACjD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;QACvC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;QACpF,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAA;QACpC,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,6CAA6C,CAAC,CAAA;QACnG,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9G,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,cAAc,EAAE,CAAA;QAC3C,MAAM,OAAO,GAAG,+BAA+B,CAAA;QAC/C,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAA;QAC1C,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAA;QAC1C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,+BAA+B,CAAC,CAAA;QAChF,KAAK,CAAC,IAAK,CAAC,IAAI,GAAG,SAAS,CAAA,CAAC,iBAAiB;QAC9C,KAAK,CAAC,IAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,OAAO,CAAA;QAC3C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QACzC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAA;QAC3D,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAChD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,YAAY,MAAM,IAAI,OAAO,mBAAmB,IAAI,EAAE,EAAE,IAAI,CAAC,CAAA;QAC7G,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,OAAO,CAAC,WAAW,GAAG,KAAK,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAA,CAAC,CAAC,CAAA;QAClH,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,CAAC,mBAAmB,CAAC,+BAA+B,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAA,CAAC,CAAC,CAAC,EACjG,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CACjC,CAAA;QACD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,OAAO,CAAC,WAAW,GAAG,KAAK,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA,CAAC,CAAC,CAAA;QACxG,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,CAAC,mBAAmB,CAAC,+BAA+B,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAA,CAAC,CAAC,CAAC,EACjG,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CACjC,CAAA;QACD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,gFAAgF,EAAE,KAAK,IAAI,EAAE;QAC9F,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,mBAAmB,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACrF,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAW,CAAA;QACvE,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,IAAI,kBAAkB,GAAG,CAAC,CAAA;QAC1B,IAAI,iBAAiB,GAAG,CAAC,CACxB;QAAC,OAAe,CAAC,WAAW,GAAG;YAC9B,aAAa,EAAE,KAAK,IAAI,EAAE;gBACxB,kBAAkB,EAAE,CAAA;gBACpB,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,iBAAiB,EAAE,CAAC,CAAC,OAAO,GAAG,CAAA,CAAC,CAAC,EAAE,CAAA;YACpE,CAAC;SACF,CAAA;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC1F,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAClC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAClC,+EAA+E;QAC/E,qEAAqE;QACrE,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAA;QACnC,2DAA2D;QAC3D,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACnF,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,mBAAmB,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACrF,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAW,CAAA;QACvE,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,IAAI,UAAU,GAAG,IAAI,CACpB;QAAC,OAAe,CAAC,WAAW,GAAG;YAC9B,aAAa,EAAE,KAAK,IAAI,EAAE;gBACxB,IAAI,CAAC,UAAU;oBAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAA;gBACrG,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAA;YACpC,CAAC;SACF,CAAA;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC1F,+DAA+D;QAC/D,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAC5D,iDAAiD;QACjD,UAAU,GAAG,KAAK,CAAA;QAClB,sEAAsE;QACtE,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { describe, it } from 'node:test'\nimport { strict as assert } from 'assert'\nimport { generateKeyPairSync } from 'node:crypto'\nimport jwt from 'jsonwebtoken'\nimport { SessionHandler } 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.verifyToken = async () => {\n calls.count++\n return structuredClone(testUser)\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.verifyToken = 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.verifyToken = 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)._jwksClient = {\n getSigningKey: async () => {\n getSigningKeyCalls++\n return { getPublicKey: () => { getPublicKeyCalls++; return pem } }\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)._jwksClient = {\n getSigningKey: async () => {\n if (!keyPresent) throw Object.assign(new Error('key not found'), { name: 'SigningKeyNotFoundError' })\n return { getPublicKey: () => pem }\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"]}
|