@data-fair/lib-node 2.17.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -9
- package/events-queue.js +4 -4
- package/events-queue.js.map +1 -1
- package/http-agents.js +2 -1
- package/http-agents.js.map +1 -1
- package/locks.js +5 -2
- package/locks.js.map +1 -1
- package/log-err.d.ts +11 -0
- package/log-err.js +41 -0
- package/log-err.js.map +1 -0
- package/log-err.test.d.ts +1 -0
- package/log-err.test.js +64 -0
- package/log-err.test.js.map +1 -0
- package/log-worker.test.d.ts +1 -0
- package/log-worker.test.js +16 -0
- package/log-worker.test.js.map +1 -0
- package/log.d.ts +34 -0
- package/log.js +119 -0
- package/log.js.map +1 -0
- package/log.test.d.ts +1 -0
- package/log.test.js +158 -0
- package/log.test.js.map +1 -0
- package/mongo.js +5 -4
- package/mongo.js.map +1 -1
- package/observer.d.ts +0 -1
- package/observer.js +6 -25
- package/observer.js.map +1 -1
- package/package.json +9 -2
- package/session.js +2 -1
- package/session.js.map +1 -1
- package/ssrf.test.js +4 -2
- package/ssrf.test.js.map +1 -1
- package/test-fixtures/early-debug.d.ts +2 -0
- package/test-fixtures/early-debug.js +4 -0
- package/test-fixtures/early-debug.js.map +1 -0
- package/test-fixtures/log-worker.d.ts +1 -0
- package/test-fixtures/log-worker.js +6 -0
- package/test-fixtures/log-worker.js.map +1 -0
- package/test-spies.js +1 -1
- package/test-spies.js.map +1 -1
- package/upgrade-scripts.js +2 -1
- package/upgrade-scripts.js.map +1 -1
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 // 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"]}
|
|
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'\nimport log from './log.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 log.warn('failure to verify a session token', { code: 'session-token', 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/ssrf.test.js
CHANGED
|
@@ -107,8 +107,10 @@ describe('ssrf agents', () => {
|
|
|
107
107
|
it('should disable the protection with a warning behind a proxy', async () => {
|
|
108
108
|
const script = `import ${JSON.stringify(fileURLToPath(new URL('./http-agents.ts', import.meta.url)))}`;
|
|
109
109
|
const env = { ...process.env, HTTPS_PROXY: 'http://proxy.example.com:3128' };
|
|
110
|
-
const {
|
|
111
|
-
|
|
110
|
+
const { stdout } = await execFileAsync(process.execPath, ['--experimental-strip-types', '--input-type=module', '--eval', script], { env: { ...env, LOG_FORMAT: 'json' }, timeout: 10000 });
|
|
111
|
+
const line = JSON.parse(stdout.split('\n').find(l => l.includes('HTTPS_PROXY')));
|
|
112
|
+
assert.equal(line.level, 'warn');
|
|
113
|
+
assert.match(line.msg, /HTTPS_PROXY is defined, the protection against server side request forgery \(SSRF\) is disabled/);
|
|
112
114
|
});
|
|
113
115
|
});
|
|
114
116
|
describe('ssrf helpers for other clients', () => {
|
package/ssrf.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssrf.test.js","sourceRoot":"","sources":["ssrf.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, before, after } from 'node:test'\nimport { strict as assert } from 'assert'\nimport http from 'node:http'\nimport type { AddressInfo } from 'node:net'\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { fileURLToPath } from 'node:url'\nimport axios from 'axios'\nimport CacheableLookup from 'cacheable-lookup'\nimport net from 'node:net'\nimport { checkAddress, parseIPList, getSsrfRules, getProxyEnvName, resolvePublicAddress, publicLookup, SsrfHttpAgent, SsrfHttpsAgent, type SsrfRules } from './ssrf.js'\n\nconst execFileAsync = promisify(execFile)\n\nconst noRules: SsrfRules = { publicIPs: [], privateIPs: [] }\n\ndescribe('ssrf address checks', () => {\n it('should refuse non public addresses', () => {\n for (const address of [\n '127.0.0.1', '10.1.2.3', '172.16.0.1', '192.168.1.1', '100.64.0.1', '169.254.169.254', '0.0.0.0',\n '255.255.255.255', '224.0.0.1', '::1', '::', 'fd00::1', 'fe80::1', '::ffff:127.0.0.1', '::ffff:169.254.169.254',\n '64:ff9b::a9fe:a9fe', '2002:a9fe:a9fe::1'\n ]) {\n assert.ok(checkAddress(address, undefined, noRules), `${address} should be refused`)\n }\n })\n\n it('should accept public addresses and ignore hostnames', () => {\n for (const address of ['8.8.8.8', '141.95.149.122', '2001:4860:4860::8888', '::ffff:8.8.8.8', 'koumoul.com']) {\n assert.equal(checkAddress(address, undefined, noRules), undefined, `${address} should be accepted`)\n }\n })\n\n it('should extend public and private addresses from env variables', () => {\n const rules = getSsrfRules({ SSRF_PUBLIC_IPS: '10.0.0.0/8, 192.168.1.1', SSRF_PRIVATE_IPS: '141.95.149.120/30,2001:4860:4860::8888' })\n assert.equal(checkAddress('10.1.2.3', undefined, rules), undefined)\n assert.equal(checkAddress('::ffff:10.1.2.3', undefined, rules), undefined)\n assert.equal(checkAddress('192.168.1.1', undefined, rules), undefined)\n assert.ok(checkAddress('192.168.1.2', undefined, rules))\n assert.ok(checkAddress('141.95.149.122', undefined, rules)?.message.includes('SSRF_PRIVATE_IPS'))\n assert.equal(checkAddress('141.95.149.124', undefined, rules), undefined)\n assert.ok(checkAddress('2001:4860:4860::8888', undefined, rules))\n })\n\n it('should name the env variable to change in the error message', () => {\n const err = checkAddress('10.1.2.3', 'internal.example.com', noRules)\n assert.ok(err?.message.includes('internal.example.com (10.1.2.3)'))\n assert.ok(err?.message.includes('SSRF_PUBLIC_IPS'))\n assert.equal(err?.code, 'ERR_SSRF_BLOCKED')\n })\n\n it('should reject invalid lists', () => {\n assert.throws(() => parseIPList('10.0.0.0/8,not-an-ip', 'SSRF_PUBLIC_IPS'), /not-an-ip.*SSRF_PUBLIC_IPS/)\n assert.deepEqual(parseIPList(' , ', 'SSRF_PUBLIC_IPS'), [])\n })\n\n it('should detect proxy env variables', () => {\n assert.equal(getProxyEnvName({}), undefined)\n assert.equal(getProxyEnvName({ https_proxy: 'http://proxy:3128' }), 'https_proxy')\n })\n})\n\ndescribe('ssrf agents', () => {\n let server: http.Server\n let port: number\n\n before(async () => {\n server = http.createServer((req, res) => {\n if (req.url === '/redirect') {\n res.writeHead(302, { location: `http://127.0.0.2:${port}/` })\n return res.end()\n }\n res.end('ok')\n })\n await new Promise<void>(resolve => server.listen(0, '0.0.0.0', resolve))\n port = (server.address() as AddressInfo).port\n })\n\n after(() => { server.close() })\n\n // same setup as http-agents.ts\n const buildAgents = (rules: SsrfRules) => {\n const httpAgent = new SsrfHttpAgent({ keepAlive: true }, rules)\n const httpsAgent = new SsrfHttpsAgent({ keepAlive: true }, rules)\n const cacheableLookup = new CacheableLookup()\n cacheableLookup.install(httpAgent)\n cacheableLookup.install(httpsAgent)\n return { httpAgent, httpsAgent, proxy: false as const }\n }\n\n it('should refuse IP literals before connecting', async () => {\n await assert.rejects(axios.get(`http://127.0.0.1:${port}/`, buildAgents(noRules)), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(axios.get('https://169.254.169.254/', buildAgents(noRules)), { code: 'ERR_SSRF_BLOCKED' })\n })\n\n it('should refuse hostnames resolving to non public addresses', async () => {\n await assert.rejects(axios.get(`http://localhost:${port}/`, buildAgents(noRules)), { code: 'ERR_SSRF_BLOCKED' })\n })\n\n it('should accept addresses declared public', async () => {\n const agents = buildAgents(getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.1,::1' }))\n assert.equal((await axios.get(`http://127.0.0.1:${port}/`, agents)).data, 'ok')\n assert.equal((await axios.get(`http://localhost:${port}/`, agents)).data, 'ok')\n })\n\n it('should check every redirection', async () => {\n const agents = buildAgents(getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.1' }))\n await assert.rejects(axios.get(`http://127.0.0.1:${port}/redirect`, agents), { code: 'ERR_SSRF_BLOCKED' })\n const allAgents = buildAgents(getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.0/8' }))\n assert.equal((await axios.get(`http://127.0.0.1:${port}/redirect`, allAgents)).data, 'ok')\n })\n\n it('should guard the default lib instance, not the private one', async () => {\n const script = `\n import { axiosInstance, privateAxiosInstance } from ${JSON.stringify(fileURLToPath(new URL('./axios.ts', import.meta.url)))}\n const url = 'http://localhost:${port}/'\n try { await axiosInstance.get(url); console.log('public:allowed') } catch (err) { console.log('public:' + err.code) }\n console.log('private:' + (await privateAxiosInstance.get(url)).data)\n `\n const env = { ...process.env, SSRF_PUBLIC_IPS: '', HTTP_PROXY: '', HTTPS_PROXY: '', ALL_PROXY: '', http_proxy: '', https_proxy: '', all_proxy: '' }\n const { stdout } = await execFileAsync(process.execPath, ['--experimental-strip-types', '--input-type=module', '--eval', script], { env, timeout: 10000 })\n assert.match(stdout, /public:ERR_SSRF_BLOCKED/)\n assert.match(stdout, /private:ok/)\n })\n\n it('should disable the protection with a warning behind a proxy', async () => {\n const script = `import ${JSON.stringify(fileURLToPath(new URL('./http-agents.ts', import.meta.url)))}`\n const env = { ...process.env, HTTPS_PROXY: 'http://proxy.example.com:3128' }\n const { stderr } = await execFileAsync(process.execPath, ['--experimental-strip-types', '--input-type=module', '--eval', script], { env, timeout: 10000 })\n assert.match(stderr, /WARNING: HTTPS_PROXY is defined, the protection against server side request forgery \\(SSRF\\) is disabled/)\n })\n})\n\ndescribe('ssrf helpers for other clients', () => {\n it('should resolve and check hosts', async () => {\n await assert.rejects(resolvePublicAddress('localhost', noRules), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(resolvePublicAddress('127.0.0.1', noRules), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(resolvePublicAddress('[::1]', noRules), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(resolvePublicAddress('169.254.169.254'), { code: 'ERR_SSRF_BLOCKED' })\n assert.equal(await resolvePublicAddress('8.8.8.8', noRules), '8.8.8.8')\n assert.equal(await resolvePublicAddress('[2001:4860:4860::8888]', noRules), '2001:4860:4860::8888')\n const loopback = getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.1,::1' })\n assert.ok(['127.0.0.1', '::1'].includes(await resolvePublicAddress('localhost', loopback)))\n })\n\n it('should refuse non public addresses in a lookup option', async () => {\n assert.ok(!process.env.SSRF_PUBLIC_IPS, 'the test must run without SSRF_PUBLIC_IPS')\n const err = await new Promise<any>((resolve) => {\n const socket = net.connect({ host: 'localhost', port: 1, lookup: publicLookup })\n socket.on('error', resolve)\n socket.on('connect', () => { socket.destroy(); resolve(undefined) })\n })\n assert.equal(err?.code, 'ERR_SSRF_BLOCKED')\n const legacyErr = await new Promise<any>(resolve => publicLookup('localhost', resolve))\n assert.equal(legacyErr?.code, 'ERR_SSRF_BLOCKED')\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"ssrf.test.js","sourceRoot":"","sources":["ssrf.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, before, after } from 'node:test'\nimport { strict as assert } from 'assert'\nimport http from 'node:http'\nimport type { AddressInfo } from 'node:net'\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { fileURLToPath } from 'node:url'\nimport axios from 'axios'\nimport CacheableLookup from 'cacheable-lookup'\nimport net from 'node:net'\nimport { checkAddress, parseIPList, getSsrfRules, getProxyEnvName, resolvePublicAddress, publicLookup, SsrfHttpAgent, SsrfHttpsAgent, type SsrfRules } from './ssrf.js'\n\nconst execFileAsync = promisify(execFile)\n\nconst noRules: SsrfRules = { publicIPs: [], privateIPs: [] }\n\ndescribe('ssrf address checks', () => {\n it('should refuse non public addresses', () => {\n for (const address of [\n '127.0.0.1', '10.1.2.3', '172.16.0.1', '192.168.1.1', '100.64.0.1', '169.254.169.254', '0.0.0.0',\n '255.255.255.255', '224.0.0.1', '::1', '::', 'fd00::1', 'fe80::1', '::ffff:127.0.0.1', '::ffff:169.254.169.254',\n '64:ff9b::a9fe:a9fe', '2002:a9fe:a9fe::1'\n ]) {\n assert.ok(checkAddress(address, undefined, noRules), `${address} should be refused`)\n }\n })\n\n it('should accept public addresses and ignore hostnames', () => {\n for (const address of ['8.8.8.8', '141.95.149.122', '2001:4860:4860::8888', '::ffff:8.8.8.8', 'koumoul.com']) {\n assert.equal(checkAddress(address, undefined, noRules), undefined, `${address} should be accepted`)\n }\n })\n\n it('should extend public and private addresses from env variables', () => {\n const rules = getSsrfRules({ SSRF_PUBLIC_IPS: '10.0.0.0/8, 192.168.1.1', SSRF_PRIVATE_IPS: '141.95.149.120/30,2001:4860:4860::8888' })\n assert.equal(checkAddress('10.1.2.3', undefined, rules), undefined)\n assert.equal(checkAddress('::ffff:10.1.2.3', undefined, rules), undefined)\n assert.equal(checkAddress('192.168.1.1', undefined, rules), undefined)\n assert.ok(checkAddress('192.168.1.2', undefined, rules))\n assert.ok(checkAddress('141.95.149.122', undefined, rules)?.message.includes('SSRF_PRIVATE_IPS'))\n assert.equal(checkAddress('141.95.149.124', undefined, rules), undefined)\n assert.ok(checkAddress('2001:4860:4860::8888', undefined, rules))\n })\n\n it('should name the env variable to change in the error message', () => {\n const err = checkAddress('10.1.2.3', 'internal.example.com', noRules)\n assert.ok(err?.message.includes('internal.example.com (10.1.2.3)'))\n assert.ok(err?.message.includes('SSRF_PUBLIC_IPS'))\n assert.equal(err?.code, 'ERR_SSRF_BLOCKED')\n })\n\n it('should reject invalid lists', () => {\n assert.throws(() => parseIPList('10.0.0.0/8,not-an-ip', 'SSRF_PUBLIC_IPS'), /not-an-ip.*SSRF_PUBLIC_IPS/)\n assert.deepEqual(parseIPList(' , ', 'SSRF_PUBLIC_IPS'), [])\n })\n\n it('should detect proxy env variables', () => {\n assert.equal(getProxyEnvName({}), undefined)\n assert.equal(getProxyEnvName({ https_proxy: 'http://proxy:3128' }), 'https_proxy')\n })\n})\n\ndescribe('ssrf agents', () => {\n let server: http.Server\n let port: number\n\n before(async () => {\n server = http.createServer((req, res) => {\n if (req.url === '/redirect') {\n res.writeHead(302, { location: `http://127.0.0.2:${port}/` })\n return res.end()\n }\n res.end('ok')\n })\n await new Promise<void>(resolve => server.listen(0, '0.0.0.0', resolve))\n port = (server.address() as AddressInfo).port\n })\n\n after(() => { server.close() })\n\n // same setup as http-agents.ts\n const buildAgents = (rules: SsrfRules) => {\n const httpAgent = new SsrfHttpAgent({ keepAlive: true }, rules)\n const httpsAgent = new SsrfHttpsAgent({ keepAlive: true }, rules)\n const cacheableLookup = new CacheableLookup()\n cacheableLookup.install(httpAgent)\n cacheableLookup.install(httpsAgent)\n return { httpAgent, httpsAgent, proxy: false as const }\n }\n\n it('should refuse IP literals before connecting', async () => {\n await assert.rejects(axios.get(`http://127.0.0.1:${port}/`, buildAgents(noRules)), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(axios.get('https://169.254.169.254/', buildAgents(noRules)), { code: 'ERR_SSRF_BLOCKED' })\n })\n\n it('should refuse hostnames resolving to non public addresses', async () => {\n await assert.rejects(axios.get(`http://localhost:${port}/`, buildAgents(noRules)), { code: 'ERR_SSRF_BLOCKED' })\n })\n\n it('should accept addresses declared public', async () => {\n const agents = buildAgents(getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.1,::1' }))\n assert.equal((await axios.get(`http://127.0.0.1:${port}/`, agents)).data, 'ok')\n assert.equal((await axios.get(`http://localhost:${port}/`, agents)).data, 'ok')\n })\n\n it('should check every redirection', async () => {\n const agents = buildAgents(getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.1' }))\n await assert.rejects(axios.get(`http://127.0.0.1:${port}/redirect`, agents), { code: 'ERR_SSRF_BLOCKED' })\n const allAgents = buildAgents(getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.0/8' }))\n assert.equal((await axios.get(`http://127.0.0.1:${port}/redirect`, allAgents)).data, 'ok')\n })\n\n it('should guard the default lib instance, not the private one', async () => {\n const script = `\n import { axiosInstance, privateAxiosInstance } from ${JSON.stringify(fileURLToPath(new URL('./axios.ts', import.meta.url)))}\n const url = 'http://localhost:${port}/'\n try { await axiosInstance.get(url); console.log('public:allowed') } catch (err) { console.log('public:' + err.code) }\n console.log('private:' + (await privateAxiosInstance.get(url)).data)\n `\n const env = { ...process.env, SSRF_PUBLIC_IPS: '', HTTP_PROXY: '', HTTPS_PROXY: '', ALL_PROXY: '', http_proxy: '', https_proxy: '', all_proxy: '' }\n const { stdout } = await execFileAsync(process.execPath, ['--experimental-strip-types', '--input-type=module', '--eval', script], { env, timeout: 10000 })\n assert.match(stdout, /public:ERR_SSRF_BLOCKED/)\n assert.match(stdout, /private:ok/)\n })\n\n it('should disable the protection with a warning behind a proxy', async () => {\n const script = `import ${JSON.stringify(fileURLToPath(new URL('./http-agents.ts', import.meta.url)))}`\n const env = { ...process.env, HTTPS_PROXY: 'http://proxy.example.com:3128' }\n const { stdout } = await execFileAsync(process.execPath, ['--experimental-strip-types', '--input-type=module', '--eval', script], { env: { ...env, LOG_FORMAT: 'json' }, timeout: 10000 })\n const line = JSON.parse(stdout.split('\\n').find(l => l.includes('HTTPS_PROXY')) as string)\n assert.equal(line.level, 'warn')\n assert.match(line.msg, /HTTPS_PROXY is defined, the protection against server side request forgery \\(SSRF\\) is disabled/)\n })\n})\n\ndescribe('ssrf helpers for other clients', () => {\n it('should resolve and check hosts', async () => {\n await assert.rejects(resolvePublicAddress('localhost', noRules), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(resolvePublicAddress('127.0.0.1', noRules), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(resolvePublicAddress('[::1]', noRules), { code: 'ERR_SSRF_BLOCKED' })\n await assert.rejects(resolvePublicAddress('169.254.169.254'), { code: 'ERR_SSRF_BLOCKED' })\n assert.equal(await resolvePublicAddress('8.8.8.8', noRules), '8.8.8.8')\n assert.equal(await resolvePublicAddress('[2001:4860:4860::8888]', noRules), '2001:4860:4860::8888')\n const loopback = getSsrfRules({ SSRF_PUBLIC_IPS: '127.0.0.1,::1' })\n assert.ok(['127.0.0.1', '::1'].includes(await resolvePublicAddress('localhost', loopback)))\n })\n\n it('should refuse non public addresses in a lookup option', async () => {\n assert.ok(!process.env.SSRF_PUBLIC_IPS, 'the test must run without SSRF_PUBLIC_IPS')\n const err = await new Promise<any>((resolve) => {\n const socket = net.connect({ host: 'localhost', port: 1, lookup: publicLookup })\n socket.on('error', resolve)\n socket.on('connect', () => { socket.destroy(); resolve(undefined) })\n })\n assert.equal(err?.code, 'ERR_SSRF_BLOCKED')\n const legacyErr = await new Promise<any>(resolve => publicLookup('localhost', resolve))\n assert.equal(legacyErr?.code, 'ERR_SSRF_BLOCKED')\n })\n})\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"early-debug.js","sourceRoot":"","sources":["early-debug.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,OAAO,WAAW,MAAM,OAAO,CAAA;AAE/B,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA","sourcesContent":["// a debug instance created before the logger module is loaded\nimport createDebug from 'debug'\n\nexport const earlyDebug = createDebug('lt:early')\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// worker thread that restores the log context received from its parent and logs inside it
|
|
2
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
3
|
+
import log, { _resetLogger, withLogContext } from '../log.js';
|
|
4
|
+
_resetLogger({ format: 'json', destination: { write: (s) => { parentPort?.postMessage(s); } } });
|
|
5
|
+
withLogContext(workerData.logContext, () => log.info('in worker'));
|
|
6
|
+
//# sourceMappingURL=log-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-worker.js","sourceRoot":"","sources":["log-worker.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,GAAG,EAAE,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE7D,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACvG,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAA","sourcesContent":["// worker thread that restores the log context received from its parent and logs inside it\nimport { parentPort, workerData } from 'node:worker_threads'\nimport log, { _resetLogger, withLogContext } from '../log.js'\n\n_resetLogger({ format: 'json', destination: { write: (s: string) => { parentPort?.postMessage(s) } } })\nwithLogContext(workerData.logContext, () => log.info('in worker'))\n"]}
|
package/test-spies.js
CHANGED
|
@@ -33,7 +33,7 @@ export function waitFor(eventName, timeout = 2000) {
|
|
|
33
33
|
export function emit(eventName, data) {
|
|
34
34
|
debug(`Emitting test spy ${eventName} (${pendingPromises[eventName]?.length} listeners)`);
|
|
35
35
|
if (!pendingPromises[eventName]?.length) {
|
|
36
|
-
|
|
36
|
+
debug(`No listener for test spy ${eventName}`);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
for (const p of pendingPromises[eventName]) {
|
package/test-spies.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-spies.js","sourceRoot":"","sources":["test-spies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,WAAW,MAAM,OAAO,CAAA;AAE/B,MAAM,KAAK,GAAG,WAAW,CAAC,YAAY,CAAC,CAAA;AAEvC,MAAM,UAAU,mBAAmB;IACjC,QAAQ,CAAC,0CAA0C,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;AACtF,CAAC;AAED,MAAM,EAAE,eAAe,EAAE,GAAG,KAAK,CAAA;AAEjC,MAAM,eAAe,GAAmE,EAAE,CAAA;AAC1F,IAAI,CAAC,GAAG,CAAC,CAAA;AAET,MAAM,UAAU,OAAO,CAAM,SAAiB,EAAE,OAAO,GAAG,IAAI;IAC5D,CAAC,EAAE,CAAA;IACH,MAAM,EAAE,GAAG,CAAC,CAAA;IAEZ,KAAK,CAAC,wBAAwB,SAAS,QAAQ,EAAE,cAAc,OAAO,KAAK,CAAC,CAAA;IAE5E,sBAAsB;IACtB,wEAAwE;IACxE,KAAK,CAAC,eAAe,GAAG,CAAC,CAAA;IACzB,MAAM,YAAY,GAAG,IAAI,KAAK,CAAC,gCAAgC,SAAS,EAAE,CAAC,CAAA;IAC3E,KAAK,CAAC,eAAe,GAAG,eAAe,CAAA;IACvC,KAAK,CAAC,iBAAiB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAE9C,MAAM,CAAC,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,eAAe,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;QAC7D,eAAe,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAA;QAChD,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IACF,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,wBAAwB,SAAS,QAAQ,EAAE,GAAG,CAAC,CAAA;QACrD,eAAe,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QAChF,IAAI,eAAe,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IACF,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,IAAI,CAAE,SAAiB,EAAE,IAAS;IAChD,KAAK,CAAC,qBAAqB,SAAS,KAAK,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,aAAa,CAAC,CAAA;IACzF,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACxC,
|
|
1
|
+
{"version":3,"file":"test-spies.js","sourceRoot":"","sources":["test-spies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,WAAW,MAAM,OAAO,CAAA;AAE/B,MAAM,KAAK,GAAG,WAAW,CAAC,YAAY,CAAC,CAAA;AAEvC,MAAM,UAAU,mBAAmB;IACjC,QAAQ,CAAC,0CAA0C,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;AACtF,CAAC;AAED,MAAM,EAAE,eAAe,EAAE,GAAG,KAAK,CAAA;AAEjC,MAAM,eAAe,GAAmE,EAAE,CAAA;AAC1F,IAAI,CAAC,GAAG,CAAC,CAAA;AAET,MAAM,UAAU,OAAO,CAAM,SAAiB,EAAE,OAAO,GAAG,IAAI;IAC5D,CAAC,EAAE,CAAA;IACH,MAAM,EAAE,GAAG,CAAC,CAAA;IAEZ,KAAK,CAAC,wBAAwB,SAAS,QAAQ,EAAE,cAAc,OAAO,KAAK,CAAC,CAAA;IAE5E,sBAAsB;IACtB,wEAAwE;IACxE,KAAK,CAAC,eAAe,GAAG,CAAC,CAAA;IACzB,MAAM,YAAY,GAAG,IAAI,KAAK,CAAC,gCAAgC,SAAS,EAAE,CAAC,CAAA;IAC3E,KAAK,CAAC,eAAe,GAAG,eAAe,CAAA;IACvC,KAAK,CAAC,iBAAiB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAE9C,MAAM,CAAC,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,eAAe,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;QAC7D,eAAe,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAA;QAChD,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IACF,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,wBAAwB,SAAS,QAAQ,EAAE,GAAG,CAAC,CAAA;QACrD,eAAe,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QAChF,IAAI,eAAe,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IACF,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,IAAI,CAAE,SAAiB,EAAE,IAAS;IAChD,KAAK,CAAC,qBAAqB,SAAS,KAAK,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,aAAa,CAAC,CAAA;IACzF,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACxC,KAAK,CAAC,4BAA4B,SAAS,EAAE,CAAC,CAAA;QAC9C,OAAM;IACR,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3C,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACjB,CAAC;AACH,CAAC","sourcesContent":["import { register } from 'node:module'\nimport debugModule from 'debug'\n\nconst debug = debugModule('test-spies')\n\nexport function registerModuleHooks () {\n register('@data-fair/lib-node/test-module-hooks.js', { parentURL: import.meta.url })\n}\n\nconst { stackTraceLimit } = Error\n\nconst pendingPromises: Record<string, { resolve: (data: any) => void, id: number }[]> = {}\nlet i = 0\n\nexport function waitFor <T> (eventName: string, timeout = 2000) {\n i++\n const id = i\n\n debug(`Waiting for test spy ${eventName} (id=${id}, timeout: ${timeout}ms)`)\n\n // better stack traces\n // see https://github.com/axios/axios/issues/2387#issuecomment-652242713\n Error.stackTraceLimit = 0\n const timeoutError = new Error(`Timeout waiting for test spy ${eventName}`)\n Error.stackTraceLimit = stackTraceLimit\n Error.captureStackTrace(timeoutError, waitFor)\n\n const p = new Promise<T>((resolve, reject) => {\n pendingPromises[eventName] = pendingPromises[eventName] ?? []\n pendingPromises[eventName].push({ resolve, id })\n setTimeout(() => reject(timeoutError), timeout)\n })\n p.finally(() => {\n debug(`Cleaning up test spy ${eventName} (id=${id})`)\n pendingPromises[eventName] = pendingPromises[eventName].filter(p => p.id !== id)\n if (pendingPromises[eventName].length === 0) delete pendingPromises[eventName]\n })\n return p\n}\n\nexport function emit (eventName: string, data: any) {\n debug(`Emitting test spy ${eventName} (${pendingPromises[eventName]?.length} listeners)`)\n if (!pendingPromises[eventName]?.length) {\n debug(`No listener for test spy ${eventName}`)\n return\n }\n for (const p of pendingPromises[eventName]) {\n p.resolve(data)\n }\n}\n"]}
|
package/upgrade-scripts.js
CHANGED
|
@@ -2,6 +2,7 @@ import semver from 'semver';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
4
4
|
import Debug from 'debug';
|
|
5
|
+
import log from './log.js';
|
|
5
6
|
const debug = Debug('upgrade');
|
|
6
7
|
/**
|
|
7
8
|
* Run pending upgrade scripts for the current service, then record the
|
|
@@ -20,7 +21,7 @@ const debug = Debug('upgrade');
|
|
|
20
21
|
export default async function (db, locks, basePath = './', isFresh) {
|
|
21
22
|
const ack = await locks.acquire('upgrade');
|
|
22
23
|
if (!ack) {
|
|
23
|
-
|
|
24
|
+
log.warn('upgrade scripts lock is already acquired, skip them');
|
|
24
25
|
// IMPORTANT: this behaviour of running the process when the upgrade scripts are still running on another one
|
|
25
26
|
// implies that they cannot be considered a pre-requisite..
|
|
26
27
|
// if we want to consider the upgrade scripts as a pre-requisite we should implement a wait on all
|
package/upgrade-scripts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade-scripts.js","sourceRoot":"","sources":["upgrade-scripts.ts"],"names":[],"mappings":"AAEA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AACxD,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"upgrade-scripts.js","sourceRoot":"","sources":["upgrade-scripts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Db } from 'mongodb'\nimport type { Debugger } from 'debug'\nimport semver from 'semver'\nimport path from 'path'\nimport { readdirSync, readFileSync, statSync } from 'fs'\nimport Debug from 'debug'\nimport type { Locks } from './locks.js'\nimport log from './log.js'\n\nconst debug = Debug('upgrade')\n\n/**\n * A migration to be applied to the service's database.\n *\n * Scripts live in `upgrade/<last-released-version>/<filename>.ts` under the\n * service. The folder name MUST be the version of the last release at the\n * time the script is authored — never an anticipated future version. On a\n * working branch the eventual release number is unknown (the same change may\n * ship as a minor, a major, or be backported), so the last known release is\n * the only stable reference.\n *\n * The runner executes every script whose folder version satisfies\n * `semver.gte(folder, dbVersion)`, where `dbVersion` is what was stored at\n * the previous startup, then records the current `package.json` version.\n * With folder = last-released, the script runs once on production upgrade\n * (after the release bumps `package.json`), and re-runs on every staging\n * deploy until the next release ships — this is expected.\n *\n * `exec` MUST be idempotent: re-running it must be a safe no-op.\n */\nexport interface UpgradeScript {\n /** One short sentence; logged at run time. */\n description: string\n /** Idempotent migration body. */\n exec: (db: Db, debug: Debugger) => Promise<void>\n}\n\n/**\n * Run pending upgrade scripts for the current service, then record the\n * current `package.json` version in the `services` Mongo collection.\n *\n * Acquires the `upgrade` lock so only one process runs migrations at a time;\n * other processes log a warning and continue without waiting.\n *\n * See the `UpgradeScript` interface for the folder-naming rule.\n *\n * @param db - Active Mongo connection (reused by the scripts).\n * @param locks - Lock manager from `@data-fair/lib-node/locks`.\n * @param basePath - Path to the service directory containing `upgrade/` and `package.json`. Defaults to `./`.\n * @param isFresh - Optional callback returning true if the database is a brand-new install with no legacy data. When true, all historical scripts are skipped and only the current version is recorded.\n */\nexport default async function (db: Db, locks: Locks, basePath = './', isFresh?: () => Promise<boolean>) {\n const ack = await locks.acquire('upgrade')\n if (!ack) {\n log.warn('upgrade scripts lock is already acquired, skip them')\n // IMPORTANT: this behaviour of running the process when the upgrade scripts are still running on another one\n // implies that they cannot be considered a pre-requisite..\n // if we want to consider the upgrade scripts as a pre-requisite we should implement a wait on all\n // containers for the scripts that are running in only 1 (while loop on \"acquire\" ?) and a healthcheck\n // are not considered \"up\" and the previous versions keep running in the mean time\n } else {\n try {\n await runScripts(db, basePath, isFresh)\n } finally {\n await locks.release('upgrade')\n }\n }\n}\n\nasync function runScripts (db: Db, basePath: string, isFresh?: () => Promise<boolean>) {\n const dir = path.resolve(basePath)\n // in the case of workspaces the parent package.json is the right place to look for name and versions\n const parentPjsonPath = path.join(dir, '../package.json')\n const pjsonPath = path.join(dir, 'package.json')\n let pjson\n try {\n pjson = JSON.parse(readFileSync(parentPjsonPath, 'utf8'))\n debug('read service info from ' + parentPjsonPath)\n } catch (err) {\n pjson = JSON.parse(readFileSync(pjsonPath, 'utf8'))\n debug('read service info from ' + pjsonPath)\n }\n\n const scriptsRoot = path.join(dir, 'upgrade')\n debug(`service=${pjson.name} version=${pjson.version}`)\n\n const services = db.collection<{ id: string, version: string }>('services')\n const service = await services.findOne({ id: pjson.name })\n let previousPersion = service?.version\n debug('list scripts from ' + scriptsRoot)\n let scripts = await listScripts(scriptsRoot)\n if (previousPersion) {\n debug(`current service version from database : ${previousPersion}`)\n } else {\n debug('no service version found in database, this is the first time the upgrade system is run')\n scripts = scripts.filter(scriptDef => scriptDef.version === 'init')\n if (isFresh) {\n if (await isFresh()) {\n debug('isFresh function returned true, this is a fresh install, do not run any script')\n } else {\n debug('isFresh function returned false, this is not a fresh install, run all init scripts')\n previousPersion = '0.0.0'\n }\n } else {\n debug('no isFresh function, it could be a fresh install, do not run any script')\n }\n }\n\n if (previousPersion) {\n for (const scriptDef of scripts) {\n if (semver.gte(scriptDef.version, previousPersion)) {\n for (const scriptName of scriptDef.names) {\n const script: UpgradeScript = (await import(path.join(scriptsRoot, scriptDef.version, scriptName))).default\n debug('apply script %s/%s : %s', scriptDef.version, scriptName, script.description)\n await script.exec(db, Debug(`upgrade:${scriptDef.version}:${scriptName}`))\n }\n }\n }\n }\n\n const coercedVersion = semver.coerce(pjson.version)\n if (!coercedVersion) throw new Error(`Invalid version number in package.json ${pjson.version}`)\n const newService = { id: pjson.name, version: coercedVersion.version }\n debug(`upgrade scripts are over, save current version number ${newService.version}`)\n await services.updateOne({ id: pjson.name }, { $set: newService }, { upsert: true })\n}\n\n// Walk the scripts directories\nasync function listScripts (scriptsRoot: string) {\n const dirs = readdirSync(scriptsRoot)\n .filter(dir => statSync(path.join(scriptsRoot, dir)).isDirectory())\n .sort()\n return dirs.map(dir => {\n return {\n version: dir,\n names: readdirSync(path.join(scriptsRoot, dir)).sort()\n }\n })\n}\n"]}
|