@edynamix/mf-isolation 0.0.10 → 0.0.12

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 CHANGED
@@ -98,16 +98,24 @@ Routes are module-relative and contain no leading slash. The plugin validates th
98
98
  For standalone development, combine the manifest with the host-owned navigation browser entry and development BFF:
99
99
 
100
100
  ```ts
101
- import { mfDevelopmentBffPlugin } from '@edynamix/mf-isolation/vite-development';
101
+ import {
102
+ MF_DEVELOPMENT_HOST_NAVIGATION_PATH,
103
+ mfDevelopmentBffPlugin,
104
+ } from '@edynamix/mf-isolation/vite-development';
102
105
 
103
106
  mfDevelopmentBffPlugin({
104
107
  hostNavigationUrl: 'https://host.example.test/host-navigation/index.js',
105
108
  moduleApiOrigin: 'https://module.example.test',
106
109
  moduleApiPath: '/stockmasterapi/',
107
110
  });
111
+
112
+ const localHostNavigationUrl = new URL(
113
+ MF_DEVELOPMENT_HOST_NAVIGATION_PATH,
114
+ 'https://localhost:4200',
115
+ ).href;
108
116
  ```
109
117
 
110
- `hostNavigationUrl` selects the host environment. Host API calls go to that origin, while module API calls go to the explicit `moduleApiOrigin`. The BFF performs OIDC authorization code with PKCE, retains tokens and module API cookies in the development process, and forwards authenticated API requests without exposing either to module JavaScript. It also forwards the host-validated navigation selection through `X-Edyn-Navigation-Group-Id` and `X-Edyn-Navigation-Dealer-Id`; module backends must still authorize those values before constructing module-specific session state.
118
+ `hostNavigationUrl` selects the host environment. Load the browser entry from `localHostNavigationUrl`; the BFF serves the selected bundle and its host-owned logo assets through the secure local origin, avoiding mixed-content failures when the selected host is an HTTP localhost instance. Host API calls go to the selected host origin, while module API calls go to the explicit `moduleApiOrigin`. The BFF performs OIDC authorization code with PKCE, retains tokens and module API cookies in the development process, and forwards authenticated API requests without exposing either to module JavaScript. It also forwards the host-validated navigation selection through `X-Edyn-Navigation-Group-Id` and `X-Edyn-Navigation-Dealer-Id`; module backends must still authorize those values before constructing module-specific session state.
111
119
 
112
120
  ## Angular CDK overlays
113
121
 
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import type { Plugin } from 'vite';
3
+ export declare const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = "/api/dev-host-navigation/index.js";
3
4
  export interface IMfDevelopmentBffPluginOptions {
4
5
  developmentOrigin?: string;
5
6
  hostNavigationUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"vite-development.d.ts","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AA2EnC,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAsoBD,eAAO,MAAM,sBAAsB,GAAI,SAAS,8BAA8B,KAAG,MAWhF,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;AAExI,eAAO,MAAM,gCAAgC,GAAI,SAAS,8BAA8B,KAAG,2BAK1F,CAAC","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must look like /moduleapi/.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n writeJson(response, 401, { code: 'missing_access_token', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
1
+ {"version":3,"file":"vite-development.d.ts","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAQnC,eAAO,MAAM,mCAAmC,sCAAsC,CAAC;AAuEvF,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAwsBD,eAAO,MAAM,sBAAsB,GAAI,SAAS,8BAA8B,KAAG,MAWhF,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;AAExI,eAAO,MAAM,gCAAgC,GAAI,SAAS,8BAA8B,KAAG,2BAK1F,CAAC","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nexport const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';\nconst HOST_NAVIGATION_ASSET_PATHS = new Set(['/exsto-icon.svg', '/exsto-text.svg']);\nconst MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;\nconst MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must look like /moduleapi/.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n name.startsWith(':') ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nconst readResponseBody = async (response: Response, maximumBytes: number, label: string): Promise<Buffer> => {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {\n await response.body?.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n if (!response.body) {\n return Buffer.alloc(0);\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n totalBytes += value.byteLength;\n if (totalBytes > maximumBytes) {\n await reader.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, totalBytes);\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #hostNavigationUrl: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostNavigationUrl = hostNavigation;\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {\n await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');\n return;\n }\n if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {\n await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #hostNavigationResource(response: ServerResponse, upstreamUrl: URL, kind: 'asset' | 'script'): Promise<void> {\n const upstream = await fetch(upstreamUrl, {\n headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },\n redirect: 'error',\n });\n if (!upstream.ok) {\n throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);\n }\n const contentType = upstream.headers.get('content-type') ?? '';\n const validContentType =\n kind === 'script' ? /^(?:text|application)\\/javascript(?:;|$)/i.test(contentType) : /^image\\/svg\\+xml(?:;|$)/i.test(contentType);\n if (!validContentType) {\n throw new Error(`Host navigation ${kind} returned an invalid content type.`);\n }\n const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;\n const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', contentType);\n response.setHeader('X-Content-Type-Options', 'nosniff');\n response.end(body);\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n writeJson(response, 401, { code: 'missing_access_token', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
@@ -7,6 +7,10 @@ const LOGOUT_COOKIE = '__Host-edyn_oidc_logout';
7
7
  const CALLBACK_PATH = '/callback';
8
8
  const LOGGED_OUT_PATH = '/logged-out';
9
9
  const LOGIN_PATH = '/api/dev-auth/login';
10
+ export const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';
11
+ const HOST_NAVIGATION_ASSET_PATHS = new Set(['/exsto-icon.svg', '/exsto-text.svg']);
12
+ const MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;
13
+ const MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;
10
14
  const SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;
11
15
  const FLOW_MAX_AGE_SECONDS = 10 * 60;
12
16
  const TOKEN_REFRESH_SKEW_MS = 30_000;
@@ -139,6 +143,7 @@ const proxyHeaders = (request, accessToken, moduleCookies, navigationContext) =>
139
143
  const headers = new Headers();
140
144
  for (const [name, value] of Object.entries(request.headers)) {
141
145
  if (!value ||
146
+ name.startsWith(':') ||
142
147
  [
143
148
  'authorization',
144
149
  'connection',
@@ -229,11 +234,43 @@ const copyProxyResponse = async (upstream, response) => {
229
234
  readable.pipe(response);
230
235
  });
231
236
  };
237
+ const readResponseBody = async (response, maximumBytes, label) => {
238
+ const declaredLength = Number(response.headers.get('content-length'));
239
+ if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
240
+ await response.body?.cancel();
241
+ throw new Error(`${label} exceeded the configured size limit.`);
242
+ }
243
+ if (!response.body) {
244
+ return Buffer.alloc(0);
245
+ }
246
+ const reader = response.body.getReader();
247
+ const chunks = [];
248
+ let totalBytes = 0;
249
+ try {
250
+ while (true) {
251
+ const { done, value } = await reader.read();
252
+ if (done) {
253
+ break;
254
+ }
255
+ totalBytes += value.byteLength;
256
+ if (totalBytes > maximumBytes) {
257
+ await reader.cancel();
258
+ throw new Error(`${label} exceeded the configured size limit.`);
259
+ }
260
+ chunks.push(value);
261
+ }
262
+ }
263
+ finally {
264
+ reader.releaseLock();
265
+ }
266
+ return Buffer.concat(chunks, totalBytes);
267
+ };
232
268
  class MfDevelopmentBff {
233
269
  #configurationUrl;
234
270
  #developmentOrigin;
235
271
  #flows = new Map();
236
272
  #hostOrigin;
273
+ #hostNavigationUrl;
237
274
  #jwks = new Map();
238
275
  #logoutFlows = new Map();
239
276
  #moduleApiOrigin;
@@ -243,6 +280,7 @@ class MfDevelopmentBff {
243
280
  #discoveryPromise = null;
244
281
  constructor(options) {
245
282
  const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);
283
+ this.#hostNavigationUrl = hostNavigation;
246
284
  this.#hostOrigin = new URL(hostNavigation.origin);
247
285
  this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);
248
286
  this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');
@@ -263,6 +301,14 @@ class MfDevelopmentBff {
263
301
  const url = new URL(request.url, this.#developmentOrigin);
264
302
  this.#prune();
265
303
  try {
304
+ if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {
305
+ await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');
306
+ return;
307
+ }
308
+ if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {
309
+ await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');
310
+ return;
311
+ }
266
312
  if (request.method === 'GET' && url.pathname === LOGIN_PATH) {
267
313
  await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));
268
314
  return;
@@ -301,6 +347,27 @@ class MfDevelopmentBff {
301
347
  writeJson(response, 502, { code: 'development_authentication_failed', error: message });
302
348
  }
303
349
  }
350
+ async #hostNavigationResource(response, upstreamUrl, kind) {
351
+ const upstream = await fetch(upstreamUrl, {
352
+ headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },
353
+ redirect: 'error',
354
+ });
355
+ if (!upstream.ok) {
356
+ throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);
357
+ }
358
+ const contentType = upstream.headers.get('content-type') ?? '';
359
+ const validContentType = kind === 'script' ? /^(?:text|application)\/javascript(?:;|$)/i.test(contentType) : /^image\/svg\+xml(?:;|$)/i.test(contentType);
360
+ if (!validContentType) {
361
+ throw new Error(`Host navigation ${kind} returned an invalid content type.`);
362
+ }
363
+ const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;
364
+ const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);
365
+ response.statusCode = 200;
366
+ response.setHeader('Cache-Control', 'no-store');
367
+ response.setHeader('Content-Type', contentType);
368
+ response.setHeader('X-Content-Type-Options', 'nosniff');
369
+ response.end(body);
370
+ }
304
371
  async #configuration() {
305
372
  this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })
306
373
  .then(async (response) => {
@@ -1 +1 @@
1
- {"version":3,"file":"vite-development.js","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must look like /moduleapi/.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n writeJson(response, 401, { code: 'missing_access_token', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
1
+ {"version":3,"file":"vite-development.js","sourceRoot":"","sources":["../src/vite-development.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport type { Plugin } from 'vite';\n\nconst SESSION_COOKIE = '__Host-edyn_dev_session';\nconst FLOW_COOKIE = '__Host-edyn_oidc_flow';\nconst LOGOUT_COOKIE = '__Host-edyn_oidc_logout';\nconst CALLBACK_PATH = '/callback';\nconst LOGGED_OUT_PATH = '/logged-out';\nconst LOGIN_PATH = '/api/dev-auth/login';\nexport const MF_DEVELOPMENT_HOST_NAVIGATION_PATH = '/api/dev-host-navigation/index.js';\nconst HOST_NAVIGATION_ASSET_PATHS = new Set(['/exsto-icon.svg', '/exsto-text.svg']);\nconst MAX_HOST_NAVIGATION_BYTES = 5 * 1024 * 1024;\nconst MAX_HOST_NAVIGATION_ASSET_BYTES = 512 * 1024;\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst FLOW_MAX_AGE_SECONDS = 10 * 60;\nconst TOKEN_REFRESH_SKEW_MS = 30_000;\nconst NAVIGATION_GROUP_HEADER = 'X-Edyn-Navigation-Group-Id';\nconst NAVIGATION_DEALER_HEADER = 'X-Edyn-Navigation-Dealer-Id';\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[\\x20-\\x7e]*$/;\nconst ALLOWED_HOST_API_PATHS = [\n '/api/auth/me',\n '/api/navigation',\n '/api/navigation/access-check',\n '/api/navigation/access-request',\n '/api/navigation/counter-stream',\n] as const;\n\ninterface IOidcClientConfiguration {\n clientId: string;\n issuer: string;\n scope: string;\n}\n\ninterface IOidcDiscovery {\n authorizationEndpoint: string;\n endSessionEndpoint: string;\n issuer: string;\n jwksUri: string;\n revocationEndpoint: string | null;\n tokenEndpoint: string;\n}\n\ninterface IOidcFlow {\n codeVerifier: string;\n createdAt: number;\n nonce: string;\n returnTo: string;\n state: string;\n}\n\ninterface IModuleNavigationContext {\n dealerId: number;\n groupId: number;\n}\n\ninterface ITokenSession {\n accessToken: string;\n expiresAt: number;\n idToken: string;\n moduleBootstrapTail: Promise<void>;\n moduleCookies: Map<string, string>;\n moduleNavigationContext: IModuleNavigationContext | null;\n refreshPromise?: Promise<void>;\n refreshToken: string | null;\n}\n\ninterface ILogoutFlow {\n createdAt: number;\n idToken: string | null;\n}\n\ninterface ITokenResponse {\n accessToken: string;\n expiresIn: number;\n idToken: string | null;\n refreshToken: string | null;\n tokenType: string;\n}\n\nexport interface IMfDevelopmentBffPluginOptions {\n developmentOrigin?: string;\n hostNavigationUrl: string;\n moduleApiOrigin: string;\n moduleApiPath: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst requiredString = (value: Record<string, unknown>, key: string): string => {\n const candidate = value[key];\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response is missing ${key}.`);\n }\n return candidate;\n};\n\nconst optionalString = (value: Record<string, unknown>, key: string): string | null => {\n const candidate = value[key];\n if (candidate === undefined || candidate === null) {\n return null;\n }\n if (typeof candidate !== 'string' || candidate.trim() === '') {\n throw new Error(`OIDC response contains an invalid ${key}.`);\n }\n return candidate;\n};\n\nconst absoluteHttpsUrl = (candidate: string, label: string, allowLocalHttp = false): URL => {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new Error(`${label} must be an absolute URL.`);\n }\n const isLocalHttp = allowLocalHttp && url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1');\n if (url.protocol !== 'https:' && !isLocalHttp) {\n throw new Error(`${label} must use HTTPS.`);\n }\n if (url.username || url.password || url.hash) {\n throw new Error(`${label} must not contain credentials or a fragment.`);\n }\n return url;\n};\n\nconst moduleApiPrefix = (candidate: string): string => {\n if (!/^\\/[a-z][a-z0-9-]*api\\/$/.test(candidate)) {\n throw new Error('moduleApiPath must look like /moduleapi/.');\n }\n return candidate;\n};\n\nconst base64Url = (size = 32): string => randomBytes(size).toString('base64url');\nconst codeChallengeOf = (verifier: string): string => createHash('sha256').update(verifier).digest('base64url');\n\nconst cookiesOf = (request: IncomingMessage): ReadonlyMap<string, string> => {\n const cookies = new Map<string, string>();\n for (const part of (request.headers.cookie ?? '').split(';')) {\n const separator = part.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name && value) {\n cookies.set(name, value);\n }\n }\n return cookies;\n};\n\nconst cookie = (name: string, value: string, maxAge: number): string => `${name}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;\n\nconst clearCookie = (name: string): string => cookie(name, '', 0);\n\nconst writeJson = (response: ServerResponse, status: number, body: unknown): void => {\n response.statusCode = status;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(JSON.stringify(body));\n};\n\nconst redirect = (response: ServerResponse, location: string, cookies: string[] = []): void => {\n response.statusCode = 302;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Location', location);\n if (cookies.length > 0) {\n response.setHeader('Set-Cookie', cookies);\n }\n response.end();\n};\n\nconst safeReturnTo = (candidate: string | null): string => {\n if (!candidate?.startsWith('/') || candidate.startsWith('//')) {\n return '/';\n }\n try {\n const parsed = new URL(candidate, 'https://localhost');\n return `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n return '/';\n }\n};\n\nconst parseTokenResponse = (value: unknown, requireIdToken: boolean): ITokenResponse => {\n if (!isRecord(value)) {\n throw new Error('OIDC token endpoint returned an invalid response.');\n }\n const expiresIn = value.expires_in;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw new Error('OIDC token endpoint returned an invalid expires_in value.');\n }\n const idToken = optionalString(value, 'id_token');\n if (requireIdToken && !idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const tokenType = requiredString(value, 'token_type');\n if (tokenType.toLowerCase() !== 'bearer') {\n throw new Error('OIDC token endpoint returned an unsupported token type.');\n }\n return {\n accessToken: requiredString(value, 'access_token'),\n expiresIn,\n idToken,\n refreshToken: optionalString(value, 'refresh_token'),\n tokenType,\n };\n};\n\nconst pathIsHostApi = (path: string): boolean => {\n return ALLOWED_HOST_API_PATHS.some((candidate) => path === candidate || path.startsWith(`${candidate}/`));\n};\n\nconst proxyHeaders = (\n request: IncomingMessage,\n accessToken: string,\n moduleCookies?: Map<string, string>,\n navigationContext?: IModuleNavigationContext | null,\n): Headers => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (\n !value ||\n name.startsWith(':') ||\n [\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'host',\n 'origin',\n 'transfer-encoding',\n NAVIGATION_GROUP_HEADER.toLowerCase(),\n NAVIGATION_DEALER_HEADER.toLowerCase(),\n ].includes(name.toLowerCase())\n ) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (moduleCookies?.size) {\n headers.set('Cookie', [...moduleCookies.values()].join('; '));\n }\n if (navigationContext) {\n headers.set(NAVIGATION_GROUP_HEADER, String(navigationContext.groupId));\n headers.set(NAVIGATION_DEALER_HEADER, String(navigationContext.dealerId));\n }\n return headers;\n};\n\nconst navigationContextIn = (value: unknown): IModuleNavigationContext | null => {\n if (!isRecord(value) || !isRecord(value.context)) {\n return null;\n }\n const groupId = value.context.selectedGroupId;\n const dealerId = value.context.selectedDealerId;\n if (!Number.isSafeInteger(groupId) || (groupId as number) <= 0 || !Number.isSafeInteger(dealerId) || (dealerId as number) < 0) {\n return null;\n }\n return { dealerId: dealerId as number, groupId: groupId as number };\n};\n\nconst updateModuleCookies = (cookies: Map<string, string>, headers: Headers): void => {\n const values = headers.getSetCookie();\n for (const value of values) {\n if (value.length > 4096) {\n continue;\n }\n const [pair, ...attributes] = value.split(';');\n const separator = pair?.indexOf('=') ?? -1;\n if (!pair || separator <= 0) {\n continue;\n }\n const name = pair.slice(0, separator).trim();\n const cookieValue = pair.slice(separator + 1).trim();\n if (!COOKIE_NAME_PATTERN.test(name) || !COOKIE_VALUE_PATTERN.test(cookieValue) || cookieValue.includes(';')) {\n continue;\n }\n const deleted = cookieValue === '' || attributes.some((attribute) => /^\\s*max-age\\s*=\\s*0\\s*$/i.test(attribute));\n if (deleted) {\n cookies.delete(name);\n continue;\n }\n if (!cookies.has(name) && cookies.size >= 32) {\n continue;\n }\n cookies.set(name, `${name}=${cookieValue}`);\n }\n};\n\nconst copyProxyResponse = async (upstream: Response, response: ServerResponse): Promise<void> => {\n response.statusCode = upstream.status;\n for (const [name, value] of upstream.headers) {\n if (!['connection', 'content-encoding', 'content-length', 'set-cookie', 'transfer-encoding'].includes(name.toLowerCase())) {\n response.setHeader(name, value);\n }\n }\n response.setHeader('Cache-Control', upstream.headers.get('cache-control') ?? 'no-store');\n if (!upstream.body) {\n response.end();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const readable = Readable.fromWeb(upstream.body as never);\n readable.once('error', reject);\n response.once('error', reject);\n response.once('finish', resolve);\n readable.pipe(response);\n });\n};\n\nconst readResponseBody = async (response: Response, maximumBytes: number, label: string): Promise<Buffer> => {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {\n await response.body?.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n if (!response.body) {\n return Buffer.alloc(0);\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n totalBytes += value.byteLength;\n if (totalBytes > maximumBytes) {\n await reader.cancel();\n throw new Error(`${label} exceeded the configured size limit.`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, totalBytes);\n};\n\nclass MfDevelopmentBff {\n readonly #configurationUrl: URL;\n readonly #developmentOrigin: URL;\n readonly #flows = new Map<string, IOidcFlow>();\n readonly #hostOrigin: URL;\n readonly #hostNavigationUrl: URL;\n readonly #jwks = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n readonly #logoutFlows = new Map<string, ILogoutFlow>();\n readonly #moduleApiOrigin: URL;\n readonly #moduleApiPath: string;\n readonly #sessions = new Map<string, ITokenSession>();\n #configurationPromise: Promise<IOidcClientConfiguration> | null = null;\n #discoveryPromise: Promise<IOidcDiscovery> | null = null;\n\n constructor(options: IMfDevelopmentBffPluginOptions) {\n const hostNavigation = absoluteHttpsUrl(options.hostNavigationUrl, 'hostNavigationUrl', true);\n this.#hostNavigationUrl = hostNavigation;\n this.#hostOrigin = new URL(hostNavigation.origin);\n this.#configurationUrl = new URL('/api/auth/oidc-configuration', this.#hostOrigin);\n this.#developmentOrigin = absoluteHttpsUrl(options.developmentOrigin ?? 'https://localhost:4200', 'developmentOrigin');\n if (this.#developmentOrigin.pathname !== '/' || this.#developmentOrigin.search || this.#developmentOrigin.hash) {\n throw new Error('developmentOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiOrigin = absoluteHttpsUrl(options.moduleApiOrigin, 'moduleApiOrigin', true);\n if (this.#moduleApiOrigin.pathname !== '/' || this.#moduleApiOrigin.search || this.#moduleApiOrigin.hash) {\n throw new Error('moduleApiOrigin must be an origin without a path, query, or fragment.');\n }\n this.#moduleApiPath = moduleApiPrefix(options.moduleApiPath);\n }\n\n async handle(request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void): Promise<void> {\n if (!request.url || !request.method) {\n next();\n return;\n }\n const url = new URL(request.url, this.#developmentOrigin);\n this.#prune();\n\n try {\n if (request.method === 'GET' && url.pathname === MF_DEVELOPMENT_HOST_NAVIGATION_PATH) {\n await this.#hostNavigationResource(response, this.#hostNavigationUrl, 'script');\n return;\n }\n if (request.method === 'GET' && HOST_NAVIGATION_ASSET_PATHS.has(url.pathname)) {\n await this.#hostNavigationResource(response, new URL(url.pathname, this.#hostOrigin), 'asset');\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGIN_PATH) {\n await this.#login(request, response, safeReturnTo(url.searchParams.get('returnTo')));\n return;\n }\n if (request.method === 'GET' && url.pathname === CALLBACK_PATH) {\n await this.#callback(request, response, url);\n return;\n }\n if (request.method === 'GET' && url.pathname === LOGGED_OUT_PATH) {\n const returnTo = safeReturnTo(url.searchParams.get('state'));\n redirect(response, `${LOGIN_PATH}?returnTo=${encodeURIComponent(returnTo)}`, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n if (request.method === 'GET' && url.pathname === '/frontchannel-logout') {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n response.statusCode = 204;\n response.setHeader('Set-Cookie', clearCookie(SESSION_COOKIE));\n response.end();\n return;\n }\n if (request.method === 'POST' && url.pathname === '/api/auth/logout') {\n await this.#logout(request, response);\n return;\n }\n if (pathIsHostApi(url.pathname) || url.pathname.startsWith(this.#moduleApiPath)) {\n await this.#proxy(request, response, url);\n return;\n }\n next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Development authentication failed.';\n writeJson(response, 502, { code: 'development_authentication_failed', error: message });\n }\n }\n\n async #hostNavigationResource(response: ServerResponse, upstreamUrl: URL, kind: 'asset' | 'script'): Promise<void> {\n const upstream = await fetch(upstreamUrl, {\n headers: { Accept: kind === 'script' ? 'text/javascript, application/javascript' : 'image/svg+xml' },\n redirect: 'error',\n });\n if (!upstream.ok) {\n throw new Error(`Host navigation ${kind} failed (${upstream.status}).`);\n }\n const contentType = upstream.headers.get('content-type') ?? '';\n const validContentType =\n kind === 'script' ? /^(?:text|application)\\/javascript(?:;|$)/i.test(contentType) : /^image\\/svg\\+xml(?:;|$)/i.test(contentType);\n if (!validContentType) {\n throw new Error(`Host navigation ${kind} returned an invalid content type.`);\n }\n const maximumBytes = kind === 'script' ? MAX_HOST_NAVIGATION_BYTES : MAX_HOST_NAVIGATION_ASSET_BYTES;\n const body = await readResponseBody(upstream, maximumBytes, `Host navigation ${kind}`);\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Content-Type', contentType);\n response.setHeader('X-Content-Type-Options', 'nosniff');\n response.end(body);\n }\n\n async #configuration(): Promise<IOidcClientConfiguration> {\n this.#configurationPromise ??= fetch(this.#configurationUrl, { headers: { Accept: 'application/json' }, redirect: 'error' })\n .then(async (response) => {\n if (!response.ok) {\n throw new Error(`Host OIDC configuration failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('Host OIDC configuration returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC issuer', true).href.replace(/\\/$/, '');\n return { clientId: requiredString(value, 'clientId'), issuer, scope: requiredString(value, 'scope') };\n })\n .catch((error) => {\n this.#configurationPromise = null;\n throw error;\n });\n return this.#configurationPromise;\n }\n\n async #discovery(): Promise<IOidcDiscovery> {\n this.#discoveryPromise ??= this.#configuration()\n .then(async (configuration) => {\n const response = await fetch(`${configuration.issuer}/.well-known/openid-configuration`, {\n headers: { Accept: 'application/json' },\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC discovery failed (${response.status}).`);\n }\n const value: unknown = await response.json();\n if (!isRecord(value)) {\n throw new Error('OIDC discovery returned an invalid response.');\n }\n const issuer = absoluteHttpsUrl(requiredString(value, 'issuer'), 'OIDC discovery issuer', true).href.replace(/\\/$/, '');\n if (issuer !== configuration.issuer) {\n throw new Error('OIDC discovery issuer does not match the host configuration.');\n }\n const endpoint = (key: string): string => {\n const url = absoluteHttpsUrl(requiredString(value, key), `OIDC ${key}`, true);\n if (url.origin !== new URL(issuer).origin) {\n throw new Error(`OIDC ${key} must use the configured issuer origin.`);\n }\n return url.href;\n };\n const revocation = optionalString(value, 'revocation_endpoint');\n return {\n authorizationEndpoint: endpoint('authorization_endpoint'),\n endSessionEndpoint: endpoint('end_session_endpoint'),\n issuer,\n jwksUri: endpoint('jwks_uri'),\n revocationEndpoint: revocation ? endpoint('revocation_endpoint') : null,\n tokenEndpoint: endpoint('token_endpoint'),\n };\n })\n .catch((error) => {\n this.#discoveryPromise = null;\n throw error;\n });\n return this.#discoveryPromise;\n }\n\n async #login(request: IncomingMessage, response: ServerResponse, returnTo: string): Promise<void> {\n const logoutId = cookiesOf(request).get(LOGOUT_COOKIE);\n if (logoutId) {\n const logout = this.#logoutFlows.get(logoutId);\n this.#logoutFlows.delete(logoutId);\n if (logout) {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const endpoint = new URL(discovery.endSessionEndpoint);\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('post_logout_redirect_uri', new URL(LOGGED_OUT_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('state', returnTo);\n if (logout.idToken) {\n endpoint.searchParams.set('id_token_hint', logout.idToken);\n }\n redirect(response, endpoint.href, [clearCookie(LOGOUT_COOKIE)]);\n return;\n }\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const flowId = base64Url();\n const codeVerifier = base64Url(48);\n const flow: IOidcFlow = { codeVerifier, createdAt: Date.now(), nonce: base64Url(), returnTo, state: base64Url() };\n this.#flows.set(flowId, flow);\n\n const endpoint = new URL(discovery.authorizationEndpoint);\n endpoint.searchParams.set('response_type', 'code');\n endpoint.searchParams.set('client_id', configuration.clientId);\n endpoint.searchParams.set('redirect_uri', new URL(CALLBACK_PATH, this.#developmentOrigin).href);\n endpoint.searchParams.set('scope', configuration.scope);\n endpoint.searchParams.set('state', flow.state);\n endpoint.searchParams.set('nonce', flow.nonce);\n endpoint.searchParams.set('code_challenge', codeChallengeOf(codeVerifier));\n endpoint.searchParams.set('code_challenge_method', 'S256');\n redirect(response, endpoint.href, [cookie(FLOW_COOKIE, flowId, FLOW_MAX_AGE_SECONDS)]);\n }\n\n async #callback(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {\n const flowId = cookiesOf(request).get(FLOW_COOKIE);\n const flow = flowId ? this.#flows.get(flowId) : undefined;\n if (flowId) {\n this.#flows.delete(flowId);\n }\n if (!flow || url.searchParams.get('state') !== flow.state) {\n writeJson(response, 400, { code: 'invalid_oidc_state', error: 'The OIDC callback state is invalid or expired.' });\n return;\n }\n const providerError = url.searchParams.get('error');\n if (providerError) {\n writeJson(response, 401, { code: providerError, error: url.searchParams.get('error_description') ?? 'Sign-in was rejected.' });\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n writeJson(response, 400, { code: 'missing_authorization_code', error: 'The OIDC callback did not include an authorization code.' });\n return;\n }\n\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const fields = new URLSearchParams({\n client_id: configuration.clientId,\n code,\n code_verifier: flow.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: new URL(CALLBACK_PATH, this.#developmentOrigin).href,\n });\n const tokenResponse = await fetch(discovery.tokenEndpoint, {\n body: fields,\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!tokenResponse.ok) {\n throw new Error(`OIDC token exchange failed (${tokenResponse.status}).`);\n }\n const tokens = parseTokenResponse(await tokenResponse.json(), true);\n const idToken = tokens.idToken;\n if (!idToken) {\n throw new Error('OIDC token endpoint did not return an ID token.');\n }\n const jwks = this.#jwks.get(discovery.jwksUri) ?? createRemoteJWKSet(new URL(discovery.jwksUri));\n this.#jwks.set(discovery.jwksUri, jwks);\n const verified = await jwtVerify(idToken, jwks, {\n audience: configuration.clientId,\n issuer: discovery.issuer,\n requiredClaims: ['sub', 'nonce'],\n });\n if (verified.payload.nonce !== flow.nonce) {\n throw new Error('OIDC ID token nonce does not match the authorization request.');\n }\n\n const sessionId = base64Url();\n this.#sessions.set(sessionId, {\n accessToken: tokens.accessToken,\n expiresAt: Date.now() + tokens.expiresIn * 1000,\n idToken,\n moduleBootstrapTail: Promise.resolve(),\n moduleCookies: new Map(),\n moduleNavigationContext: null,\n refreshToken: tokens.refreshToken,\n });\n redirect(response, flow.returnTo, [cookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE_SECONDS), clearCookie(FLOW_COOKIE)]);\n }\n\n async #logout(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n if (session) {\n void this.#revoke(session).catch(() => undefined);\n }\n const logoutId = base64Url();\n this.#logoutFlows.set(logoutId, { createdAt: Date.now(), idToken: session?.idToken ?? null });\n response.setHeader('Set-Cookie', [clearCookie(SESSION_COOKIE), cookie(LOGOUT_COOKIE, logoutId, FLOW_MAX_AGE_SECONDS)]);\n writeJson(response, 200, { loggedOut: true });\n }\n\n async #revoke(session: ITokenSession): Promise<void> {\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const revocationEndpoint = discovery.revocationEndpoint;\n if (!revocationEndpoint) {\n return;\n }\n const revoke = async (token: string, hint: string): Promise<void> => {\n await fetch(revocationEndpoint, {\n body: new URLSearchParams({ client_id: configuration.clientId, token, token_type_hint: hint }),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n };\n await Promise.all([\n revoke(session.accessToken, 'access_token'),\n session.refreshToken ? revoke(session.refreshToken, 'refresh_token') : Promise.resolve(),\n ]);\n }\n\n async #authenticatedSession(request: IncomingMessage): Promise<ITokenSession | null> {\n const sessionId = cookiesOf(request).get(SESSION_COOKIE);\n const session = sessionId ? this.#sessions.get(sessionId) : undefined;\n if (!session) {\n return null;\n }\n if (session.expiresAt > Date.now() + TOKEN_REFRESH_SKEW_MS) {\n return session;\n }\n if (!session.refreshToken) {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n session.refreshPromise ??= this.#refresh(session).finally(() => {\n session.refreshPromise = undefined;\n });\n try {\n await session.refreshPromise;\n return session;\n } catch {\n if (sessionId) {\n this.#sessions.delete(sessionId);\n }\n return null;\n }\n }\n\n async #refresh(session: ITokenSession): Promise<void> {\n const refreshToken = session.refreshToken;\n if (!refreshToken) {\n throw new Error('OIDC refresh token is unavailable.');\n }\n const [configuration, discovery] = await Promise.all([this.#configuration(), this.#discovery()]);\n const response = await fetch(discovery.tokenEndpoint, {\n body: new URLSearchParams({\n client_id: configuration.clientId,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`OIDC refresh failed (${response.status}).`);\n }\n const tokens = parseTokenResponse(await response.json(), false);\n session.accessToken = tokens.accessToken;\n session.expiresAt = Date.now() + tokens.expiresIn * 1000;\n session.refreshToken = tokens.refreshToken ?? session.refreshToken;\n session.idToken = tokens.idToken ?? session.idToken;\n }\n\n async #proxy(request: IncomingMessage, response: ServerResponse, localUrl: URL): Promise<void> {\n const session = await this.#authenticatedSession(request);\n if (!session) {\n writeJson(response, 401, { code: 'missing_access_token', error: 'Sign in is required.' });\n return;\n }\n const isModuleApi = localUrl.pathname.startsWith(this.#moduleApiPath);\n if (isModuleApi && session.moduleCookies.size === 0) {\n const previous = session.moduleBootstrapTail;\n let release = (): void => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n session.moduleBootstrapTail = previous.catch(() => undefined).then(() => current);\n await previous.catch(() => undefined);\n try {\n await this.#proxyAuthenticated(request, response, localUrl, session, true);\n } finally {\n release();\n }\n return;\n }\n await this.#proxyAuthenticated(request, response, localUrl, session, isModuleApi);\n }\n\n async #proxyAuthenticated(\n request: IncomingMessage,\n response: ServerResponse,\n localUrl: URL,\n session: ITokenSession,\n isModuleApi: boolean,\n ): Promise<void> {\n const upstreamOrigin = isModuleApi ? this.#moduleApiOrigin : this.#hostOrigin;\n const upstreamUrl = new URL(`${localUrl.pathname}${localUrl.search}`, upstreamOrigin);\n const method = request.method ?? 'GET';\n const init: RequestInit & { duplex?: 'half' } = {\n headers: proxyHeaders(\n request,\n session.accessToken,\n isModuleApi ? session.moduleCookies : undefined,\n isModuleApi ? session.moduleNavigationContext : undefined,\n ),\n method,\n redirect: 'manual',\n };\n if (method !== 'GET' && method !== 'HEAD') {\n init.body = Readable.toWeb(request) as unknown as BodyInit;\n init.duplex = 'half';\n }\n const upstream = await fetch(upstreamUrl, init);\n if (isModuleApi) {\n updateModuleCookies(session.moduleCookies, upstream.headers);\n } else if (localUrl.pathname === '/api/navigation' && upstream.ok) {\n session.moduleNavigationContext = navigationContextIn(\n await upstream\n .clone()\n .json()\n .catch(() => null),\n );\n }\n await copyProxyResponse(upstream, response);\n }\n\n #prune(): void {\n const now = Date.now();\n for (const [id, flow] of this.#flows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#flows.delete(id);\n }\n }\n for (const [id, flow] of this.#logoutFlows) {\n if (flow.createdAt + FLOW_MAX_AGE_SECONDS * 1000 < now) {\n this.#logoutFlows.delete(id);\n }\n }\n for (const [id, session] of this.#sessions) {\n if (!session.refreshToken && session.expiresAt + TOKEN_REFRESH_SKEW_MS < now) {\n this.#sessions.delete(id);\n }\n }\n }\n}\n\nexport const mfDevelopmentBffPlugin = (options: IMfDevelopmentBffPluginOptions): Plugin => {\n const middleware = createMfDevelopmentBffMiddleware(options);\n return {\n apply: 'serve',\n name: 'edynamix-mf-development-bff',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n middleware(request, response, next);\n });\n },\n };\n};\n\nexport type TMfDevelopmentBffMiddleware = (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void;\n\nexport const createMfDevelopmentBffMiddleware = (options: IMfDevelopmentBffPluginOptions): TMfDevelopmentBffMiddleware => {\n const bff = new MfDevelopmentBff(options);\n return (request, response, next) => {\n void bff.handle(request, response, next);\n };\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edynamix/mf-isolation",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "CSS and overlay isolation helpers for eDynamix microfrontends.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",