feat(connect): Skatteverket data calls through the connector with a per-company canary (#2209)
CONNECT_SKV_CANARY_COMPANIES mirrors the bank canary: the listed companies' user-token Skatteverket calls (skattekonto, moms, AGI) go through the connector's data proxy while this installation still has its own credentials and keeps refreshing the tokens on its own OAuth client. Callers without a company id (OAuth start, token exchange, environment reporting) keep the plain rule: own credentials win. This is how hosted moves its Skatteverket traffic to Connect a few companies at a time before dropping its own keys. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
This commit is contained in:
co-authored by
Jakob Wennberg
parent
65bd675f43
commit
d900fea1a8
@@ -8,7 +8,7 @@ import {
|
||||
hasOwnSkatteverketCredentials,
|
||||
} from '../upstreams'
|
||||
|
||||
const ENV = ['GNUBOK_CONNECTOR_KEY', 'GNUBOK_CONNECT_URL', 'ENABLE_BANKING_PRIVATE_KEY', 'ENABLE_BANKING_APP_ID', 'ENABLE_BANKING_PRIVATE_KEY_PRODUCTION', 'ENABLE_BANKING_APP_ID_PRODUCTION', 'SKATTEVERKET_OAUTH2_CLIENT_ID', 'SKATTEVERKET_APIGW_CLIENT_ID', 'QVALIA_API_KEY', 'QVALIA_PARTNER_REG_NO', 'CONNECT_BANK_CANARY_COMPANIES'] as const
|
||||
const ENV = ['GNUBOK_CONNECTOR_KEY', 'GNUBOK_CONNECT_URL', 'ENABLE_BANKING_PRIVATE_KEY', 'ENABLE_BANKING_APP_ID', 'ENABLE_BANKING_PRIVATE_KEY_PRODUCTION', 'ENABLE_BANKING_APP_ID_PRODUCTION', 'SKATTEVERKET_OAUTH2_CLIENT_ID', 'SKATTEVERKET_APIGW_CLIENT_ID', 'QVALIA_API_KEY', 'QVALIA_PARTNER_REG_NO', 'CONNECT_BANK_CANARY_COMPANIES', 'CONNECT_SKV_CANARY_COMPANIES'] as const
|
||||
|
||||
afterEach(() => vi.unstubAllEnvs())
|
||||
function clear() {
|
||||
@@ -91,3 +91,32 @@ describe('bank canary companies', () => {
|
||||
expect(bankConnectorMode('c-1')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('skatteverket canary companies', () => {
|
||||
it('routes only the listed companies through the connector while own credentials exist', () => {
|
||||
clear()
|
||||
vi.stubEnv('GNUBOK_CONNECTOR_KEY', 'gnubok_ck_x')
|
||||
vi.stubEnv('SKATTEVERKET_OAUTH2_CLIENT_ID', 'client')
|
||||
vi.stubEnv('SKATTEVERKET_APIGW_CLIENT_ID', 'gw')
|
||||
vi.stubEnv('CONNECT_SKV_CANARY_COMPANIES', 'c-1, c-2')
|
||||
expect(skatteverketConnectorMode()).toBeNull()
|
||||
expect(skatteverketConnectorMode('c-9')).toBeNull()
|
||||
expect(skatteverketConnectorMode('c-1')).toEqual({ baseUrl: 'https://connect.accounted.se/api/connect/skv', key: 'gnubok_ck_x' })
|
||||
expect(skatteverketConnectorMode('c-2')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not let the bank list leak into Skatteverket routing', () => {
|
||||
clear()
|
||||
vi.stubEnv('GNUBOK_CONNECTOR_KEY', 'gnubok_ck_x')
|
||||
vi.stubEnv('SKATTEVERKET_OAUTH2_CLIENT_ID', 'client')
|
||||
vi.stubEnv('CONNECT_BANK_CANARY_COMPANIES', 'c-1')
|
||||
expect(skatteverketConnectorMode('c-1')).toBeNull()
|
||||
})
|
||||
|
||||
it('ignores the canary list without a connector key', () => {
|
||||
clear()
|
||||
vi.stubEnv('SKATTEVERKET_OAUTH2_CLIENT_ID', 'client')
|
||||
vi.stubEnv('CONNECT_SKV_CANARY_COMPANIES', 'c-1')
|
||||
expect(skatteverketConnectorMode('c-1')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,29 +35,41 @@ export interface ConnectorUpstream {
|
||||
}
|
||||
|
||||
/**
|
||||
* Company ids that use the connector for bank sync even though this
|
||||
* installation has its own Enable Banking credentials: the canary switch for
|
||||
* moving an installation upstream by upstream (hosted Accounted moves its
|
||||
* bank sync to Connect a few companies at a time before dropping its own
|
||||
* keys). Comma-separated. Ignored without a connector key.
|
||||
* 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.
|
||||
*/
|
||||
function bankCanaryCompanies(): Set<string> {
|
||||
const raw = process.env.CONNECT_BANK_CANARY_COMPANIES?.trim()
|
||||
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() && !(companyId && bankCanaryCompanies().has(companyId))) return null
|
||||
if (hasOwnEnableBankingCredentials() && !isCanary('CONNECT_BANK_CANARY_COMPANIES', companyId)) return null
|
||||
return { baseUrl: `${cfg.baseUrl}/api/connect/bank`, key: cfg.key }
|
||||
}
|
||||
|
||||
export function skatteverketConnectorMode(): ConnectorUpstream | null {
|
||||
if (hasOwnSkatteverketCredentials()) return null
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user