fix(connect): PR #1758 CodeRabbit follow-up: connector status hardening, i18n strings, doc alignment (#2098)

* fix(connect): PR #1758 CodeRabbit follow-up: harden connector status, i18n the connector-mode strings, align docs

- getConnectorConfig() rebuilds baseUrl as origin + path: userinfo, query
  and fragment are stripped (warn-logged without the raw value) so nothing
  secret-shaped pasted into GNUBOK_CONNECT_URL survives into the
  /api/connector/status echo or the derived proxy URLs (CWE-200)
- /api/connector/status responds Cache-Control: no-store on both branches
  (key prefix + wiring layout out of shared browser caches, CWE-525)
- CWE-319 thread verified as no-change: both connector-mode helpers derive
  from getConnectorConfig(), which fails closed on non-https
- SkatteverketConnectPanel tooltips and BankSyncNowButton gate/upsell
  strings moved to messages/sv.json + messages/en.json keys
- DECISIONS.md: MD037 fix on line 1146 (backtick the glob), line 1147
  reworded to grants-written-wiring-pending, decision lines appended
  (incl. declining the UpgradeNote children-append suggestion)
- docs/SOVEREIGN.md availability wording aligned with SELF-HOSTING.md:
  infra merged, keys issued manually on request, client wiring pending

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKZUp1nePr8sVDoLkMSxbS

* docs(connect): skeptic follow-up: bank client wiring is merged (#2094), SKV pending, no keys issued until it lands

Skeptic refutation on PR #2098: SOVEREIGN.md claimed the services 'do not
carry traffic' while this branch already contains #2094 (EB client proxy
routing), and 'issued manually on request' contradicted the standing
no-key-before-full-PR6b rule while skatteverketConnectorMode() has no
client consumer yet. SOVEREIGN.md, SELF-HOSTING.md and DECISIONS.md line
1147 now all say: bank client wiring merged and carries traffic with a
key, Skatteverket client wiring ships in a following release, keys are
not issued until it lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKZUp1nePr8sVDoLkMSxbS

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-09-01 11:16:37 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent b74b5e3c0d
commit ca12b1855e
11 changed files with 86 additions and 19 deletions
@@ -45,6 +45,21 @@ describe('GET /api/connector/status', () => {
expect(body.data.granted_capabilities).toEqual(['bank_sync'])
})
it('is Cache-Control: no-store on both branches', async () => {
const selfHost = await GET(createMockRequest('/x'), { params: Promise.resolve({}) })
expect(selfHost.headers.get('Cache-Control')).toBe('no-store')
selfHosted = false
const hosted = await GET(createMockRequest('/x'), { params: Promise.resolve({}) })
expect(hosted.headers.get('Cache-Control')).toBe('no-store')
})
it('echoes connect_url without userinfo, query or fragment', async () => {
vi.stubEnv('GNUBOK_CONNECTOR_KEY', 'gnubok_ck_x')
vi.stubEnv('GNUBOK_CONNECT_URL', 'https://op:secret@connect.example.se/?token=secret')
const { body } = await parseJsonResponse<{ data: { connect_url: string } }>(await GET(createMockRequest('/x'), { params: Promise.resolve({}) }))
expect(body.data.connect_url).toBe('https://connect.example.se')
})
it('reports own_credentials for an upstream configured directly on the instance', async () => {
vi.stubEnv('GNUBOK_CONNECTOR_KEY', 'gnubok_ck_x')
vi.stubEnv('ENABLE_BANKING_APP_ID', 'app')
+6 -2
View File
@@ -16,10 +16,14 @@ import { getCompanyIdsWithCapability } from '@/lib/entitlements/has-capability'
* Hosted returns { self_hosted: false }: the connector product is a
* self-host-only concept. Any authenticated member may read it; it exposes no
* secret (never the key itself), only booleans and the key's non-secret prefix.
* Still no-store: the key prefix and wiring layout have no business sitting in
* a shared browser cache.
*/
const NO_STORE = { headers: { 'Cache-Control': 'no-store' } }
export const GET = withRouteContext('connector.status', async (_request, { supabase, companyId }) => {
if (!isSelfHosted()) {
return NextResponse.json({ data: { self_hosted: false } })
return NextResponse.json({ data: { self_hosted: false } }, NO_STORE)
}
const cfg = getConnectorConfig()
const bank = bankConnectorMode()
@@ -41,7 +45,7 @@ export const GET = withRouteContext('connector.status', async (_request, { supab
},
granted_capabilities: grants,
},
})
}, NO_STORE)
})
async function getConnectorGrantsFor(