Files
accounted/lib/connect/instance/upstreams.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

87 lines
3.7 KiB
TypeScript

import { getConnectorConfig } from './config'
import {
hasOwnEnableBankingCredentials,
hasOwnPeppolCredentials,
hasOwnSkatteverketCredentials,
} from '@/lib/entitlements/own-credentials'
/**
* Instance-side connector routing for the bank and Skatteverket upstreams.
*
* An upstream is in "connector mode" when this instance has a connector key
* AND no own credentials for that upstream. Hosted always has its own
* credentials, so hosted is never in connector mode: the check is what keeps
* hosted byte-identical. A self-host that pastes GNUBOK_CONNECTOR_KEY and
* leaves ENABLE_BANKING_PRIVATE_KEY / SKATTEVERKET_OAUTH2_CLIENT_ID unset gets
* routed through the hosted proxy instead.
*
* The own-credentials checks live in lib/entitlements/own-credentials.ts:
* they were forward-ported into the entitlement partition (PR #1747) so the
* gate and this routing seam can never disagree about what "own credentials"
* means. Re-exported here for the instance-side callers.
*/
export const CONNECTOR_COMPANY_HEADER = 'X-Connector-Company'
export const CONNECTOR_UPSTREAM_AUTH_HEADER = 'X-Connector-Upstream-Authorization'
export const CONNECTOR_UPSTREAM_CONTENT_TYPE_HEADER = 'X-Connector-Upstream-Content-Type'
export { hasOwnEnableBankingCredentials, hasOwnPeppolCredentials, hasOwnSkatteverketCredentials }
export interface ConnectorUpstream {
/** Base URL to send upstream requests to (the hosted proxy). */
baseUrl: string
/** The connector key, sent as Authorization: Bearer for proxy auth. */
key: string
}
/**
* Company ids that use the connector for an upstream even though this
* installation has its own credentials for it: the canary switch for moving
* an installation upstream by upstream (hosted Accounted moves its bank sync
* and its Skatteverket traffic to Connect a few companies at a time before
* dropping its own keys). Comma-separated. Ignored without a connector key.
*/
type CanaryEnv = 'CONNECT_BANK_CANARY_COMPANIES' | 'CONNECT_SKV_CANARY_COMPANIES'
function canaryCompanies(envName: CanaryEnv): Set<string> {
const raw = process.env[envName]?.trim()
if (!raw) return new Set()
return new Set(raw.split(',').map((v) => v.trim()).filter(Boolean))
}
function isCanary(envName: CanaryEnv, companyId: string | undefined): boolean {
return Boolean(companyId && canaryCompanies(envName).has(companyId))
}
export function bankConnectorMode(companyId?: string): ConnectorUpstream | null {
const cfg = getConnectorConfig()
if (!cfg) return null
if (hasOwnEnableBankingCredentials() && !isCanary('CONNECT_BANK_CANARY_COMPANIES', companyId)) return null
return { baseUrl: `${cfg.baseUrl}/api/connect/bank`, key: cfg.key }
}
/**
* Skatteverket data calls for a company. Callers without a company (OAuth
* start and token exchange, environment reporting) pass nothing and get the
* plain rule: own credentials win. The canary applies to user-token data
* reads and writes only, which is what carries a company id.
*/
export function skatteverketConnectorMode(companyId?: string): ConnectorUpstream | null {
const cfg = getConnectorConfig()
if (!cfg) return null
if (hasOwnSkatteverketCredentials() && !isCanary('CONNECT_SKV_CANARY_COMPANIES', companyId)) return null
return { baseUrl: `${cfg.baseUrl}/api/connect/skv`, key: cfg.key }
}
/**
* Peppol through Arcim's contracted access point. Same rule as the other
* upstreams: an instance with its own Qvalia partner keys runs Peppol itself
* and is never routed here.
*/
export function peppolConnectorMode(): ConnectorUpstream | null {
if (hasOwnPeppolCredentials()) return null
const cfg = getConnectorConfig()
if (!cfg) return null
return { baseUrl: `${cfg.baseUrl}/api/connect/peppol`, key: cfg.key }
}