@data-fair/lib-node 2.16.1 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locks.js +4 -1
- package/locks.js.map +1 -1
- package/package.json +1 -1
- package/session.d.ts +1 -1
- package/session.js +18 -1
- package/session.js.map +1 -1
- package/session.test.js +30 -6
- package/session.test.js.map +1 -1
package/locks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hostname } from 'node:os';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import Debug from 'debug';
|
|
4
|
+
import { internalError } from './observer.js';
|
|
4
5
|
const debug = Debug('locks');
|
|
5
6
|
export class Locks {
|
|
6
7
|
pid;
|
|
@@ -31,8 +32,10 @@ export class Locks {
|
|
|
31
32
|
db.command({ collMod: 'locks', index: { keyPattern: { updatedAt: 1 }, expireAfterSeconds: ttl } });
|
|
32
33
|
}
|
|
33
34
|
// prolongate lock acquired by this process while it is still active
|
|
35
|
+
// a mongo error here must not become an unhandled rejection (it would crash the process on any failover)
|
|
34
36
|
this.interval = setInterval(() => {
|
|
35
|
-
this.collection.updateMany({ pid: this.pid }, { $currentDate: { updatedAt: true } })
|
|
37
|
+
this.collection.updateMany({ pid: this.pid }, { $currentDate: { updatedAt: true } })
|
|
38
|
+
.catch(err => internalError('locks-heartbeat', err));
|
|
36
39
|
}, (ttl / 2) * 1000);
|
|
37
40
|
};
|
|
38
41
|
stop = async () => {
|
package/locks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locks.js","sourceRoot":"","sources":["locks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"locks.js","sourceRoot":"","sources":["locks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Db } from 'mongodb'\nimport { hostname } from 'node:os'\nimport { randomBytes } from 'node:crypto'\nimport Debug from 'debug'\nimport { internalError } from './observer.js'\n\nconst debug = Debug('locks')\n\nexport class Locks {\n pid: string\n private interval: ReturnType<typeof setInterval> | undefined\n private _db: Db | undefined\n get db () {\n if (!this._db) throw new Error('locks utils was not initialized')\n return this._db\n }\n\n get collection () {\n return this.db.collection<{ _id: string, pid: string, origin?: string, hostname: string, createdAt: Date }>('locks')\n }\n\n constructor () {\n this.pid = randomBytes(8).toString('hex')\n debug('locks with pid', this.pid)\n }\n\n start = async (db: Db, ttl = 60) => {\n if (this._db) throw new Error('locks utils was already initialized')\n this._db = db\n await this.collection.createIndex({ pid: 1 })\n try {\n await this.collection.createIndex({ updatedAt: 1 }, { expireAfterSeconds: ttl })\n } catch (err) {\n console.log('Failure to create TTL index. Probably because the value changed. Try to update it.')\n db.command({ collMod: 'locks', index: { keyPattern: { updatedAt: 1 }, expireAfterSeconds: ttl } })\n }\n\n // prolongate lock acquired by this process while it is still active\n // a mongo error here must not become an unhandled rejection (it would crash the process on any failover)\n this.interval = setInterval(() => {\n this.collection.updateMany({ pid: this.pid }, { $currentDate: { updatedAt: true } })\n .catch(err => internalError('locks-heartbeat', err))\n }, (ttl / 2) * 1000)\n }\n\n stop = async () => {\n clearInterval(this.interval)\n if (this._db) await this.collection.deleteMany({ pid: this.pid })\n }\n\n acquire = async (_id: string, origin?: string): Promise<boolean> => {\n debug('acquire', _id, origin)\n try {\n await this.collection.insertOne({ _id, pid: this.pid, origin, hostname: hostname(), createdAt: new Date() })\n try {\n await this.collection.updateOne({ _id }, { $currentDate: { updatedAt: true } })\n } catch (err) {\n await this.collection.deleteOne({ _id, pid: this.pid })\n throw err\n }\n debug('acquire ok', _id)\n return true\n } catch (err: any) {\n if (err.code !== 11000) throw err\n // duplicate means the lock was already acquired\n debug('acquire ko', _id)\n return false\n }\n }\n\n release = async (_id: string, delay = 0) => {\n debug('release', _id)\n if (delay) {\n const date = new Date((new Date()).getTime() + delay)\n await this.collection.updateOne({ _id, pid: this.pid }, { $unset: { pid: 1 }, $set: { delayed: true, updatedAt: date } })\n } else {\n await this.collection.deleteOne({ _id, pid: this.pid })\n }\n }\n}\n\nconst locks = new Locks()\n\nexport default locks\n"]}
|
package/package.json
CHANGED
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
|
-
|
|
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 }))));
|
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, 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":"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,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1D,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,iBAAiB,GAAG,KAAK,IAAI,EAAE;QACrC,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,EAAE,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAC5F,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,iBAAiB,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;QACxH,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,iBAAiB,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;QAC9G,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,YAAY,GAAG;YAC/B,OAAO,EAAE;gBACP,aAAa,EAAE,KAAK,IAAI,EAAE;oBACxB,kBAAkB,EAAE,CAAA;oBACpB,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,iBAAiB,EAAE,CAAC,CAAC,OAAO,GAAG,CAAA,CAAC,CAAC,EAAE,CAAA;gBACpE,CAAC;aACF;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,YAAY,GAAG;YAC/B,OAAO,EAAE;gBACP,aAAa,EAAE,KAAK,IAAI,EAAE;oBACxB,IAAI,CAAC,UAAU;wBAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAA;oBACrG,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAA;gBACpC,CAAC;aACF;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;AAEF,MAAM,WAAW,GAAG,mBAAmB,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;AACvE,MAAM,UAAU,GAAG,mBAAmB,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;AACtE,MAAM,QAAQ,GAAG,CAAC,UAAe,EAAE,GAAW,EAAE,OAAY,EAAE,SAAc,EAAE,EAAE,OAAY,EAAE,EAAE,EAAE,CAChG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;AAClI,MAAM,WAAW,GAAG,CAAC,OAAY,EAAE,MAAY,EAAE,IAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;AAC/H,MAAM,UAAU,GAAG,CAAC,OAAY,EAAE,MAAY,EAAE,IAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;AACpI,qEAAqE;AACrE,MAAM,WAAW,GAAG,CAAC,UAAU,GAAG,IAAI,EAAE,gBAAiC,CAAC,WAAW,CAAC,EAAE,EAAE;IACxF,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;IACpC,OAAO,CAAC,UAAU,GAAG,UAAU,CAAA;IAC/B,2EAA2E;IAC3E,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACrE,OAAO,CAAC,mBAAmB,GAAG,CAAC,KAAa,EAAE,IAAa,EAAE,EAAE,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,aAAa,IAAI,SAAS,CAAC,CAC3H;IAAC,OAAe,CAAC,mBAAmB,GAAG,KAAK,EAAE,MAAc,EAAE,EAAE,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAA;IACpI,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AACD,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE;IAClC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAClC,OAAO,YAAY,CAAC,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAA;AACjD,CAAC,CAAA;AACD,MAAM,UAAU,GAAG,CAAC,CAAe,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAA;AAE3F,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,GAAG,EAAE,4BAA4B,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IAClN,MAAM,MAAM,GAAG,CAAC,OAAY,EAAE,SAAc,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,IAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;IAE/G,EAAE,CAAC,iFAAiF,EAAE,KAAK,IAAI,EAAE;QAC/F,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;QACtE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QACrC,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,WAAW,EAAE,OAAO,CAAC,CAAA;QACxC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;IACvE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;QAC3F,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;QACtI,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,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,GAAG,QAAQ,CAAA;QAC7C,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;IACjH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACnF,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACtC,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;YAClF,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;YACpC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAA;YACzB,OAAO,CAAC,iBAAiB,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC3J,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,+BAA+B,CAAC,CAAC,CAAA;QAChF,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;QAClC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;QACvC,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAA;QACpG,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;QACrH,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAA;QAC5F,OAAO,CAAC,UAAU,GAAG,IAAI,CAAA;QACzB,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;QACnF,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAA;IACxH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;QAC3F,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,mBAAmB,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACpF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QACpF,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QAC7F,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACpE,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAC7G,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;QACtE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QACzH,KAAK,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC;YAAE,MAAM,CAAC,KAAK,CAAC,KAAK,IAAK,KAAK,CAAC,IAAe,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;IACnI,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAA;QAClH,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,EAAE,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,CAAA;QACjC,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;QAC7F,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5J,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA;IAE7H,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;QACrB,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,CAAA;QAClD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAA;QAC5C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QAC3G,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;QACtF,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,iEAAiE;QACjE,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,WAAW,CAAC,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;QAC1H,8CAA8C;QAC9C,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QAChG,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACzG,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9E,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;QACjL,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACvG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QACtG,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QAC/G,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QACpF,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;IAC7G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,KAAK,MAAM,MAAM,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YACnI,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;QACzG,CAAC;QACD,sBAAsB;QACtB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;QAChK,MAAM,CAAC,KAAK,CAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;QACvD,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QACnF,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3F,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACtC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAChH,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC/D,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,MAAM,OAAO,GAAG,CAAC,GAAU,EAAE,EAAE;QAC7B,MAAM,OAAO,GAAG,WAAW,EAAE,CAC5B;QAAC,OAAe,CAAC,mBAAmB,GAAG,KAAK,IAAI,EAAE,GAAG,MAAM,GAAG,CAAA,CAAC,CAAC,CAAA;QACjE,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;IACD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,OAAO,GAAG,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3F,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAA;IAEjI,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,KAAK,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YACvG,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;YAC5B,IAAI,OAAO,GAAG,KAAK,CAAA;YACnB,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAA,CAAC,CAAC,CAAC,EACnK,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAC7B,CAAA;YACD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAC5B,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAA;QAC1F,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAA;QACzD,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAA,CAAC,CAAC,CAAC,EACnK,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAC7B,CAAA;QACD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAC3B,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kFAAkF,EAAE,KAAK,IAAI,EAAE;QAChG,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;QACpC,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,GAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,GAAW,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA,CAAC,CAAC,EAAE,CAAC,CAClM;QAAC,OAAe,CAAC,YAAY,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAA;QACtI,MAAM,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;QACxD,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC,CAAA;IACjE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,+EAA+E,EAAE,GAAG,EAAE;IAC7F,MAAM,GAAG,GAAG,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IACxG,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,GAAW,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,MAAM,CAAA;IACjH,MAAM,MAAM,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAE1C,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,KAAK,MAAM,UAAU,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;YAC7B,IAAI,OAAO,GAAG,KAAK,CAAA;YACnB,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAA,CAAC,CAAC,CAAC,CAAC,CAAA;YACxH,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;QACvE,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IACnF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACxG,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QAC9G,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IAC7J,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gFAAgF,EAAE,KAAK,IAAI,EAAE;QAC9F,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;QAC7B,MAAM,GAAG,GAAG,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA;QAC5F,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,oBAAoB,EAAE,oBAAoB,CAAC,EAAE,CAAC;YACzE,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;YAC7F,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YAClF,MAAM,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QAC1F,CAAC;IACH,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, 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"]}
|