import { getRlsExecutionContext } from "@hivegrid/db/rls-context.server"; import { createAuditEvent } from "@hivegrid/db/audit-events.server"; import { decryptTenantField, encryptTenantField } from "@hivegrid/db/crypto/tenant-envelope.server"; import { CatalogError, foundationErrors } from "@hivegrid/foundation"; import { discoverKwpMandants, isValidKwpContextCode, KWP_APP_ID, KWP_LEGACY_MANDANT_SELECTION_KIND, KWP_MANDANT_SELECTION_HEALTH_KEY, KWP_MANDANT_SELECTION_KIND, KWP_OAUTH_PENDING_HEALTH_KEY, KWP_OAUTH_RESERVATION_KIND, KwpError, parseKwpOAuthReservationBundle, parseKwpPendingAttemptLifecycle, type KwpCredentials, type KwpMandantBinding, type KwpOAuthReservationBundle, type KwpRestorableConnectionSnapshot, type KwpRestorableConnectionStatus, } from "@hivegrid/integrations"; import { withTenantRLS, type TenantRlsClient } from "~/utils/db/.server/tenantRls.server"; import { activateOAuthConnection, AppsEngineServiceError } from "~/utils/integrations/appsEngineService.server"; import { buildKwpAuthorizationUrl, exchangeKwpAuthorizationCode, KwpOidcClientError, readKwpOidcRuntimeConfig } from "./kwpOidcClient.server"; import { createKwpOAuthChallenge, KWP_OAUTH_CHALLENGE_TTL_MS, type KwpOAuthChallengePayload, } from "./kwpOAuthChallenge.server"; const KWP_CONNECTION_LABEL = "KWP"; const KWP_SLOT_LOCK_SALT = 32_001; const KWP_NOT_CONNECTED_HEALTH_KEY = "integrations.health.notConnected"; const MAX_DISCOVERED_MANDANTS = 100; const MAX_MANDANT_LABEL_LENGTH = 256; type KwpUnconfirmedCredentials = Omit & { readonly mandantBinding?: never }; type KwpMandantOption = { readonly id: number; readonly code: string; readonly label: string; }; type KwpMandantSelectionBundle = { readonly kind: typeof KWP_MANDANT_SELECTION_KIND; readonly previous: KwpRestorableConnectionSnapshot | null; readonly oauth: KwpUnconfirmedCredentials; readonly discovery: { readonly kind: "available"; readonly options: readonly KwpMandantOption[] } | { readonly kind: "manual" }; readonly issuedAt: string; }; type PendingKwpMandantSelection = { readonly connectionId: string; readonly version: string; readonly credentialsCiphertext: string; readonly tokenExpiresAt: Date; readonly bundle: KwpMandantSelectionBundle; }; type PendingKwpOAuthReservation = { readonly connectionId: string; readonly version: string; readonly credentialsCiphertext: string; readonly tokenExpiresAt: Date; readonly bundle: KwpOAuthReservationBundle; }; type RestorablePendingKwpAttempt = { readonly connectionId: string; readonly version: string; readonly credentialsCiphertext: string; readonly tokenExpiresAt: Date; readonly healthMessageKey: typeof KWP_OAUTH_PENDING_HEALTH_KEY | typeof KWP_MANDANT_SELECTION_HEALTH_KEY; readonly previous: KwpRestorableConnectionSnapshot | null; }; export class KwpConnectError extends Error { readonly code: | "not_configured" | "invalid_state" | "slot_conflict" | "authorization_failed" | "exchange_failed" | "connection_failed" | "persistence_failed" | "invalid_selection" | "selection_expired"; readonly diagnosticCode?: string; constructor(code: KwpConnectError["code"], options?: { readonly diagnosticCode?: string }) { super(code); this.name = "KwpConnectError"; this.code = code; this.diagnosticCode = options?.diagnosticCode; } } export type BeginKwpOAuthConnectionResult = { readonly authorizationUrl: string; readonly setCookie: string; }; export type CompleteKwpOAuthConnectionResult = { readonly tenantId: string; readonly tenantSlug: string; readonly connectionId: string; }; export type KwpMandantSelectionView = { readonly connectionId: string; readonly customerCode: string; /** Exact DataConnection.updatedAt CAS fence for the selection submission. */ readonly version: string; readonly mode: "available" | "manual"; readonly options: readonly KwpMandantOption[]; }; /** Reserve/reuse the tenant's one KWP slot and create a one-time OIDC challenge. */ export async function beginKwpOAuthConnection(input: { readonly tenantId: string; readonly tenantSlug: string; readonly userId: string; readonly customerCode: string; }): Promise { assertBoundIdentity(input.tenantId, input.userId); let runtimeConfig: ReturnType; try { runtimeConfig = readKwpOidcRuntimeConfig(); } catch { throw new KwpConnectError("not_configured"); } if (runtimeConfig.hiveGridTenantId !== input.tenantId || !isValidKwpContextCode(input.customerCode) || input.customerCode !== runtimeConfig.customerCode) { throw new KwpConnectError("not_configured"); } const reservation = await ensureKwpConnectionSlot(input.tenantId, input.userId, input.customerCode); try { const challenge = createKwpOAuthChallenge({ tenantId: input.tenantId, tenantSlug: input.tenantSlug, userId: input.userId, connectionId: reservation.connectionId, connectionVersion: reservation.version, customerCode: input.customerCode, }); const authorizationUrl = await buildKwpAuthorizationUrl({ state: challenge.payload.state, nonce: challenge.payload.nonce, codeChallenge: challenge.payload.codeChallenge, customerCode: input.customerCode, }); return { authorizationUrl, setCookie: challenge.cookie }; } catch { await restorePendingKwpAttempt({ ...input, connectionId: reservation.connectionId }, reservation).catch(() => undefined); throw new KwpConnectError("authorization_failed"); } } /** * Exchange the one-time authorization code and discover the user's allowed KWP * mandates immediately while the five-minute access token is fresh. The OAuth * material is persisted only as an inert, envelope-encrypted pending bundle; * no connection becomes active before an explicit mandate binding is confirmed. */ export async function completeKwpOAuthConnection(input: { readonly callbackParameters: { readonly code: string; readonly state: string; readonly iss?: string }; readonly challenge: KwpOAuthChallengePayload; }): Promise { const { challenge } = input; assertBoundIdentity(challenge.tenantId, challenge.userId); const reservation = await loadPendingOAuthReservation({ tenantId: challenge.tenantId, userId: challenge.userId, connectionId: challenge.connectionId, expectedVersion: parseConnectionVersion(challenge.connectionVersion), }); if (isExpired(reservation)) { await restorePendingKwpAttempt(challenge, reservation); throw new KwpConnectError("invalid_state"); } let runtimeConfig: ReturnType; try { runtimeConfig = readKwpOidcRuntimeConfig(); } catch { await restorePendingKwpAttempt(challenge, reservation).catch(() => undefined); throw new KwpConnectError("not_configured"); } if ( runtimeConfig.hiveGridTenantId !== challenge.tenantId || runtimeConfig.customerCode !== challenge.customerCode || input.callbackParameters.state !== challenge.state || reservation.bundle.customerCode !== challenge.customerCode ) { await restorePendingKwpAttempt(challenge, reservation).catch(() => undefined); throw new KwpConnectError("invalid_state"); } let exchanged: { readonly credentials: KwpCredentials; readonly tokenExpiresAt: Date }; try { exchanged = await exchangeKwpAuthorizationCode(input.callbackParameters, { state: challenge.state, nonce: challenge.nonce, codeVerifier: challenge.codeVerifier, customerCode: challenge.customerCode, }); } catch (error) { await restorePendingKwpAttempt(challenge, reservation).catch(() => undefined); throw new KwpConnectError("exchange_failed", { diagnosticCode: error instanceof KwpOidcClientError ? (error.diagnosticCode ?? `oidc_${error.code.replaceAll("-", "_")}`) : "exchange_unexpected", }); } if (!Number.isFinite(exchanged.tokenExpiresAt.getTime())) { await restorePendingKwpAttempt(challenge, reservation).catch(() => undefined); throw new KwpConnectError("exchange_failed"); } const oauth = toUnconfirmedCredentials(exchanged.credentials); let discovery: KwpMandantSelectionBundle["discovery"]; try { discovery = await discoverMandantsOrManualFallback(oauth); } catch (error) { await restorePendingKwpAttempt(challenge, reservation).catch(() => undefined); throw error; } const bundle: KwpMandantSelectionBundle = { kind: KWP_MANDANT_SELECTION_KIND, previous: reservation.bundle.previous, oauth, discovery, issuedAt: new Date().toISOString(), }; try { const updated = await withTenantRLS(challenge.tenantId, async (tx) => { const credentialsCiphertext = await encryptTenantField(challenge.tenantId, JSON.stringify(bundle), { client: tx as never }); return tx.dataConnection.updateMany({ where: { id: challenge.connectionId, tenantId: challenge.tenantId, appId: KWP_APP_ID, archivedAt: null, connectionStatus: "pending", createdByUserId: challenge.userId, updatedAt: new Date(challenge.connectionVersion), credentialsCiphertext: reservation.credentialsCiphertext, tokenExpiresAt: reservation.tokenExpiresAt, healthMessageKey: KWP_OAUTH_PENDING_HEALTH_KEY, }, data: { credentialsCiphertext, tokenExpiresAt: exchanged.tokenExpiresAt, connectionStatus: "pending", healthMessageKey: KWP_MANDANT_SELECTION_HEALTH_KEY, lastHealthCheck: null, createdByUserId: challenge.userId, }, }); }); if (updated.count !== 1) throw new KwpConnectError("invalid_state"); } catch (error) { if (error instanceof KwpConnectError) throw error; await restorePendingKwpAttempt(challenge, reservation).catch(() => undefined); throw new KwpConnectError("persistence_failed"); } await auditKwpConnection(challenge.tenantId, challenge.userId, "INTEGRATION_OAUTH_MANDANT_SELECTION_PENDING", challenge.connectionId, { discovery: discovery.kind, optionCount: discovery.kind === "available" ? discovery.options.length : 0, }); return { tenantId: challenge.tenantId, tenantSlug: challenge.tenantSlug, connectionId: challenge.connectionId, }; } /** Return only the safe selection projection; OAuth material never leaves this service. */ export async function loadKwpMandantSelection(input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; }): Promise { assertBoundIdentity(input.tenantId, input.userId); const pending = await loadPendingMandantSelection(input); if (isExpired(pending)) { await restorePendingKwpAttempt(input, pending); throw new KwpConnectError("selection_expired"); } return { connectionId: pending.connectionId, customerCode: pending.bundle.oauth.customerCode, version: pending.version, mode: pending.bundle.discovery.kind, options: pending.bundle.discovery.kind === "available" ? pending.bundle.discovery.options.map((option) => ({ ...option })) : [], }; } /** Validate one explicit binding, prove it against KWP, then atomically activate. */ export async function finalizeKwpOAuthConnection(input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; readonly version: string; readonly mandantBinding: KwpMandantBinding; }): Promise<{ readonly connectionId: string }> { assertBoundIdentity(input.tenantId, input.userId); const expectedVersion = parseConnectionVersion(input.version); const pending = await loadPendingMandantSelection({ tenantId: input.tenantId, userId: input.userId, connectionId: input.connectionId, expectedVersion, }); if (isExpired(pending)) { await restorePendingKwpAttempt(input, pending); throw new KwpConnectError("selection_expired"); } const mandantBinding = resolveConfirmedBinding(pending.bundle.discovery, input.mandantBinding); const credentials: KwpCredentials = { ...pending.bundle.oauth, mandantBinding }; try { await activateOAuthConnection({ tenantId: input.tenantId, connectionId: input.connectionId, appId: KWP_APP_ID, credentials: credentials as unknown as Record, tokenExpiresAt: pending.tokenExpiresAt, actorUserId: input.userId, expectedUpdatedAt: expectedVersion, }); } catch (error) { if (error instanceof AppsEngineServiceError) { if (error.messageKey === "integrations.kwp.health.tokenInvalid") { await restorePendingKwpAttempt(input, pending); throw new KwpConnectError("selection_expired"); } throw new KwpConnectError("connection_failed", { diagnosticCode: healthDiagnosticCode(error.messageKey) }); } if (error instanceof CatalogError && error.code === foundationErrors.notFound.code) { throw new KwpConnectError("invalid_state"); } throw new KwpConnectError("persistence_failed"); } await auditKwpConnection(input.tenantId, input.userId, "INTEGRATION_KWP_MANDANT_BIND", input.connectionId, { binding: mandantBinding.kind, source: pending.bundle.discovery.kind, }); return { connectionId: input.connectionId }; } /** Explicitly abandon one selection and restore the connection from before reconnect. */ export async function cancelKwpOAuthConnectionAttempt(input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; readonly version: string; }): Promise { assertBoundIdentity(input.tenantId, input.userId); const pending = await loadRestorablePendingAttempt({ ...input, expectedVersion: parseConnectionVersion(input.version), }); await restorePendingKwpAttempt(input, pending); await auditKwpConnection(input.tenantId, input.userId, "INTEGRATION_OAUTH_CANCEL", input.connectionId, { restored: pending.previous !== null, }); } async function discoverMandantsOrManualFallback(credentials: KwpUnconfirmedCredentials): Promise { try { const options = sanitizeMandantOptions(await discoverKwpMandants(credentials)); return { kind: "available", options }; } catch (error) { if (error instanceof KwpConnectError && error.diagnosticCode === "discovery_invalid_response") { return { kind: "manual" }; } if (isManualDiscoveryFallback(error)) return { kind: "manual" }; throw new KwpConnectError("connection_failed", { diagnosticCode: discoveryDiagnosticCode(error) }); } } function isManualDiscoveryFallback(error: unknown): boolean { if (!(error instanceof KwpError)) return false; if (error.httpStatus === 401 || error.code === "auth_token_invalid" || error.code === "invalid_credentials" || error.code === "relay_host_invalid") { return false; } return ( error.httpStatus === 403 || error.httpStatus === 404 || error.code === "auth_scope_missing" || error.code === "relay_not_found" || error.code === "mandant_discovery_unavailable" || error.code === "invalid_response" || error.code === "provider_outage" || error.code === "rate_limit_exceeded" ); } function discoveryDiagnosticCode(error: unknown): string { return error instanceof KwpError ? `discovery_${error.code}` : "discovery_unexpected"; } function sanitizeMandantOptions(value: unknown): readonly KwpMandantOption[] { if (!Array.isArray(value) || value.length > MAX_DISCOVERED_MANDANTS) { throw new KwpConnectError("connection_failed", { diagnosticCode: "discovery_invalid_response" }); } const options: KwpMandantOption[] = []; const seenCodes = new Set(); for (const candidate of value) { if (!isRecord(candidate)) throw new KwpConnectError("connection_failed", { diagnosticCode: "discovery_invalid_response" }); const { id, code, label } = candidate; if ( typeof id !== "number" || !Number.isSafeInteger(id) || !isValidKwpContextCode(code) || (label !== undefined && typeof label !== "string") || (typeof label === "string" && (label.length > MAX_MANDANT_LABEL_LENGTH || label.trim() !== label)) || seenCodes.has(code) ) { throw new KwpConnectError("connection_failed", { diagnosticCode: "discovery_invalid_response" }); } seenCodes.add(code); options.push({ id, code, label: typeof label === "string" && label ? label : code }); } return options; } function toUnconfirmedCredentials(credentials: KwpCredentials): KwpUnconfirmedCredentials { return { accessToken: credentials.accessToken, refreshToken: credentials.refreshToken, expiresAt: credentials.expiresAt, issuer: credentials.issuer, clientId: credentials.clientId, clientSecret: credentials.clientSecret, customerCode: credentials.customerCode, scopes: [...credentials.scopes], }; } async function loadPendingMandantSelection(input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; readonly expectedVersion?: Date; }): Promise { try { return await withTenantRLS(input.tenantId, async (tx) => { const row = await findPendingAttemptRow(tx, input); const plaintext = await decryptTenantField(input.tenantId, row.credentialsCiphertext, { client: tx as never }); const bundle = parseMandantSelectionBundle(plaintext); if ( row.healthMessageKey !== KWP_MANDANT_SELECTION_HEALTH_KEY || row.tokenExpiresAt.toISOString() !== bundle.oauth.expiresAt ) { throw new KwpConnectError("invalid_state"); } return { connectionId: row.id, version: row.updatedAt.toISOString(), credentialsCiphertext: row.credentialsCiphertext, tokenExpiresAt: row.tokenExpiresAt, bundle, }; }); } catch (error) { if (error instanceof KwpConnectError) throw error; throw new KwpConnectError("persistence_failed"); } } async function loadPendingOAuthReservation(input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; readonly expectedVersion?: Date; }): Promise { try { return await withTenantRLS(input.tenantId, async (tx) => { const row = await findPendingAttemptRow(tx, input); const plaintext = await decryptTenantField(input.tenantId, row.credentialsCiphertext, { client: tx as never }); const bundle = parseKwpOAuthReservationBundle(plaintext); if ( !bundle || row.healthMessageKey !== KWP_OAUTH_PENDING_HEALTH_KEY || row.tokenExpiresAt.toISOString() !== bundle.expiresAt ) { throw new KwpConnectError("invalid_state"); } return { connectionId: row.id, version: row.updatedAt.toISOString(), credentialsCiphertext: row.credentialsCiphertext, tokenExpiresAt: row.tokenExpiresAt, bundle, }; }); } catch (error) { if (error instanceof KwpConnectError) throw error; throw new KwpConnectError("persistence_failed"); } } async function loadRestorablePendingAttempt(input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; readonly expectedVersion?: Date; }): Promise { try { return await withTenantRLS(input.tenantId, async (tx) => { const row = await findPendingAttemptRow(tx, input); const plaintext = await decryptTenantField(input.tenantId, row.credentialsCiphertext, { client: tx as never }); const lifecycle = parseKwpPendingAttemptLifecycle(plaintext); const expectedHealthKey = lifecycle?.phase === "oauth" ? KWP_OAUTH_PENDING_HEALTH_KEY : KWP_MANDANT_SELECTION_HEALTH_KEY; if (!lifecycle || row.healthMessageKey !== expectedHealthKey) throw new KwpConnectError("invalid_state"); return { connectionId: row.id, version: row.updatedAt.toISOString(), credentialsCiphertext: row.credentialsCiphertext, tokenExpiresAt: row.tokenExpiresAt, healthMessageKey: expectedHealthKey, previous: lifecycle.previous, }; }); } catch (error) { if (error instanceof KwpConnectError) throw error; throw new KwpConnectError("persistence_failed"); } } async function findPendingAttemptRow( tx: TenantRlsClient, input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string; readonly expectedVersion?: Date; } ) { const row = await tx.dataConnection.findFirst({ where: { id: input.connectionId, tenantId: input.tenantId, appId: KWP_APP_ID, archivedAt: null, connectionStatus: "pending", createdByUserId: input.userId, ...(input.expectedVersion ? { updatedAt: input.expectedVersion } : {}), }, select: { id: true, updatedAt: true, credentialsCiphertext: true, tokenExpiresAt: true, healthMessageKey: true, }, }); if (!row?.credentialsCiphertext || !row.tokenExpiresAt) throw new KwpConnectError("invalid_state"); return { ...row, credentialsCiphertext: row.credentialsCiphertext, tokenExpiresAt: row.tokenExpiresAt, }; } async function restorePendingKwpAttempt( input: { readonly tenantId: string; readonly userId: string; readonly connectionId: string }, pending: PendingKwpMandantSelection | PendingKwpOAuthReservation | RestorablePendingKwpAttempt ): Promise { const previous = "bundle" in pending ? pending.bundle.previous : pending.previous; const healthMessageKey = "healthMessageKey" in pending ? pending.healthMessageKey : pending.bundle.kind === KWP_OAUTH_RESERVATION_KIND ? KWP_OAUTH_PENDING_HEALTH_KEY : KWP_MANDANT_SELECTION_HEALTH_KEY; try { const restored = await withTenantRLS(input.tenantId, (tx) => tx.dataConnection.updateMany({ where: { id: input.connectionId, tenantId: input.tenantId, appId: KWP_APP_ID, archivedAt: null, connectionStatus: "pending", createdByUserId: input.userId, updatedAt: new Date(pending.version), credentialsCiphertext: pending.credentialsCiphertext, tokenExpiresAt: pending.tokenExpiresAt, healthMessageKey, }, data: { ...restorationData(previous, input.userId), // Always invalidate the browser's old callback/selection fence. updatedAt: new Date(Math.max(Date.now(), new Date(pending.version).getTime() + 1)), }, }) ); if (restored.count !== 1) throw new KwpConnectError("invalid_state"); } catch (error) { if (error instanceof KwpConnectError) throw error; throw new KwpConnectError("persistence_failed"); } } function parseMandantSelectionBundle(plaintext: string): KwpMandantSelectionBundle { let value: unknown; try { value = JSON.parse(plaintext); } catch { throw new KwpConnectError("invalid_state"); } if ( !isRecord(value) || (value.kind !== KWP_MANDANT_SELECTION_KIND && value.kind !== KWP_LEGACY_MANDANT_SELECTION_KIND) || !isRecord(value.oauth) || !isRecord(value.discovery) ) { throw new KwpConnectError("invalid_state"); } const lifecycle = parseKwpPendingAttemptLifecycle(plaintext); if (!lifecycle || lifecycle.phase !== "mandant") throw new KwpConnectError("invalid_state"); const oauth = value.oauth; if ( typeof oauth.accessToken !== "string" || !oauth.accessToken || typeof oauth.refreshToken !== "string" || !oauth.refreshToken || typeof oauth.expiresAt !== "string" || !Number.isFinite(Date.parse(oauth.expiresAt)) || typeof oauth.issuer !== "string" || !oauth.issuer || typeof oauth.clientId !== "string" || !oauth.clientId || typeof oauth.clientSecret !== "string" || !oauth.clientSecret || !isValidKwpContextCode(oauth.customerCode) || !Array.isArray(oauth.scopes) || !oauth.scopes.every((scope) => typeof scope === "string" && scope.length > 0) || oauth.mandantBinding !== undefined || typeof value.issuedAt !== "string" || !Number.isFinite(Date.parse(value.issuedAt)) ) { throw new KwpConnectError("invalid_state"); } let discovery: KwpMandantSelectionBundle["discovery"]; if (value.discovery.kind === "manual") { discovery = { kind: "manual" }; } else if (value.discovery.kind === "available") { try { discovery = { kind: "available", options: sanitizeMandantOptions(value.discovery.options) }; } catch { throw new KwpConnectError("invalid_state"); } } else { throw new KwpConnectError("invalid_state"); } return { kind: KWP_MANDANT_SELECTION_KIND, previous: lifecycle.previous, oauth: { accessToken: oauth.accessToken, refreshToken: oauth.refreshToken, expiresAt: oauth.expiresAt, issuer: oauth.issuer, clientId: oauth.clientId, clientSecret: oauth.clientSecret, customerCode: oauth.customerCode, scopes: [...oauth.scopes], }, discovery, issuedAt: value.issuedAt, }; } function resolveConfirmedBinding(discovery: KwpMandantSelectionBundle["discovery"], requested: KwpMandantBinding): KwpMandantBinding { if (discovery.kind === "available" && discovery.options.length > 0) { if (requested.kind !== "selected") throw new KwpConnectError("invalid_selection"); const option = discovery.options.find((candidate) => candidate.code === requested.code); if (!option) throw new KwpConnectError("invalid_selection"); return { kind: "selected", code: option.code, label: option.label }; } if (requested.kind === "none") return { kind: "none" }; if (requested.kind !== "selected" || !isValidKwpContextCode(requested.code) || requested.code.trim() !== requested.code) { throw new KwpConnectError("invalid_selection"); } return { kind: "selected", code: requested.code }; } function restorationData(previous: KwpRestorableConnectionSnapshot | null, actorUserId: string) { if (!previous) { return { credentialsCiphertext: null, tokenExpiresAt: null, connectionStatus: "pending" as const, healthMessageKey: KWP_NOT_CONNECTED_HEALTH_KEY, lastHealthCheck: new Date(), createdByUserId: actorUserId, }; } return { credentialsCiphertext: previous.credentialsCiphertext, tokenExpiresAt: previous.tokenExpiresAt ? new Date(previous.tokenExpiresAt) : null, connectionStatus: previous.connectionStatus, healthMessageKey: previous.healthMessageKey, lastHealthCheck: previous.lastHealthCheck ? new Date(previous.lastHealthCheck) : null, createdByUserId: previous.createdByUserId, }; } function isExpired(pending: { readonly tokenExpiresAt: Date }): boolean { return !Number.isFinite(pending.tokenExpiresAt.getTime()) || pending.tokenExpiresAt.getTime() <= Date.now(); } function parseConnectionVersion(value: string): Date { const timestamp = Date.parse(value); if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString() !== value) throw new KwpConnectError("invalid_state"); return new Date(timestamp); } function healthDiagnosticCode(messageKey: string): string { switch (messageKey) { case "integrations.kwp.health.tokenInvalid": return "health_token_invalid"; case "integrations.kwp.health.scopeMissing": return "health_scope_missing"; case "integrations.kwp.health.rateLimited": return "health_rate_limited"; case "integrations.kwp.health.providerOutage": return "health_provider_outage"; case "integrations.kwp.health.relayInvalid": return "health_relay_invalid"; case "integrations.kwp.health.credentialsInvalid": return "health_credentials_invalid"; case "integrations.kwp.health.mandantUnconfirmed": return "health_mandant_unconfirmed"; default: return "health_unknown"; } } async function ensureKwpConnectionSlot( tenantId: string, actorUserId: string, customerCode: string ): Promise { let created = false; const issuedAt = new Date(); const expiresAt = new Date(issuedAt.getTime() + KWP_OAUTH_CHALLENGE_TTL_MS); const connection = await withTenantRLS(tenantId, async (tx) => { await tx.$executeRaw`SELECT pg_advisory_xact_lock(hashtextextended(${`kwp-oauth-slot:${tenantId}`}, ${KWP_SLOT_LOCK_SALT}))`; const candidates = await tx.dataConnection.findMany({ where: { tenantId, appId: KWP_APP_ID, archivedAt: null }, select: { id: true, updatedAt: true, connectionStatus: true, credentialsCiphertext: true, tokenExpiresAt: true, healthMessageKey: true, lastHealthCheck: true, createdByUserId: true, }, orderBy: { createdAt: "asc" }, take: 2, }); if (candidates.length > 1) throw new KwpConnectError("slot_conflict"); if (candidates[0]) { const candidate = candidates[0]; if (candidate.healthMessageKey === "integrations.health.refreshInProgress") { throw new KwpConnectError("slot_conflict"); } const previous = await resolvePreviousConnectionSnapshot(tenantId, tx, candidate); const bundle: KwpOAuthReservationBundle = { kind: KWP_OAUTH_RESERVATION_KIND, customerCode, previous, issuedAt: issuedAt.toISOString(), expiresAt: expiresAt.toISOString(), }; const credentialsCiphertext = await encryptTenantField(tenantId, JSON.stringify(bundle), { client: tx as never }); const attemptVersion = new Date(Math.max(Date.now(), candidate.updatedAt.getTime() + 1)); const invalidated = await tx.dataConnection.updateMany({ where: { id: candidate.id, tenantId, appId: KWP_APP_ID, archivedAt: null, connectionStatus: candidate.connectionStatus, updatedAt: candidate.updatedAt, credentialsCiphertext: candidate.credentialsCiphertext, tokenExpiresAt: candidate.tokenExpiresAt, healthMessageKey: candidate.healthMessageKey, lastHealthCheck: candidate.lastHealthCheck, }, data: { credentialsCiphertext, tokenExpiresAt: expiresAt, connectionStatus: "pending", healthMessageKey: KWP_OAUTH_PENDING_HEALTH_KEY, lastHealthCheck: null, createdByUserId: actorUserId, updatedAt: attemptVersion, }, }); if (invalidated.count !== 1) throw new KwpConnectError("slot_conflict"); return { connectionId: candidate.id, version: attemptVersion.toISOString(), credentialsCiphertext, tokenExpiresAt: expiresAt, bundle, }; } const bundle: KwpOAuthReservationBundle = { kind: KWP_OAUTH_RESERVATION_KIND, customerCode, previous: null, issuedAt: issuedAt.toISOString(), expiresAt: expiresAt.toISOString(), }; const credentialsCiphertext = await encryptTenantField(tenantId, JSON.stringify(bundle), { client: tx as never }); const connection = await tx.dataConnection.create({ data: { tenantId, appId: KWP_APP_ID, label: KWP_CONNECTION_LABEL, credentialsCiphertext, connectionStatus: "pending", healthMessageKey: KWP_OAUTH_PENDING_HEALTH_KEY, tokenExpiresAt: expiresAt, createdByUserId: actorUserId, }, select: { id: true, updatedAt: true }, }); created = true; return { connectionId: connection.id, version: connection.updatedAt.toISOString(), credentialsCiphertext, tokenExpiresAt: expiresAt, bundle, }; }); await auditKwpConnection(tenantId, actorUserId, "INTEGRATION_OAUTH_RESERVE", connection.connectionId, { created }); return connection; } async function resolvePreviousConnectionSnapshot( tenantId: string, tx: TenantRlsClient, candidate: { readonly connectionStatus: string; readonly credentialsCiphertext: string | null; readonly tokenExpiresAt: Date | null; readonly healthMessageKey: string | null; readonly lastHealthCheck: Date | null; readonly createdByUserId: string | null; } ): Promise { if (candidate.connectionStatus === "pending") { if (!candidate.credentialsCiphertext) return null; const plaintext = await decryptTenantField(tenantId, candidate.credentialsCiphertext, { client: tx as never }); const lifecycle = parseKwpPendingAttemptLifecycle(plaintext); if (!lifecycle) throw new KwpConnectError("slot_conflict"); return lifecycle.previous; } if (!isRestorableConnectionStatus(candidate.connectionStatus)) throw new KwpConnectError("slot_conflict"); return { credentialsCiphertext: candidate.credentialsCiphertext, connectionStatus: candidate.connectionStatus, tokenExpiresAt: candidate.tokenExpiresAt?.toISOString() ?? null, healthMessageKey: candidate.healthMessageKey, lastHealthCheck: candidate.lastHealthCheck?.toISOString() ?? null, createdByUserId: candidate.createdByUserId, }; } function isRestorableConnectionStatus(value: string): value is KwpRestorableConnectionStatus { return value === "active" || value === "degraded" || value === "broken" || value === "revoked"; } async function auditKwpConnection(tenantId: string, actorId: string, action: string, targetId: string, metadata: Record): Promise { await createAuditEvent({ tenantId, actorId, actorType: "user", action, targetType: "DataConnection", targetId, sourceModule: "integrations", outcome: "success", metadata: { appId: KWP_APP_ID, ...metadata }, }).catch(() => undefined); } function assertBoundIdentity(tenantId: string, userId: string): void { const context = getRlsExecutionContext(); if (context?.tenantId !== tenantId || context.userId !== userId) { throw new KwpConnectError("invalid_state"); } } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); }