* 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>
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { withRouteContext } from '@/lib/api/with-route-context'
|
|
import { isSelfHosted } from '@/lib/env/public-flags'
|
|
import { getConnectorConfig } from '@/lib/connect/instance/config'
|
|
import { bankConnectorMode, skatteverketConnectorMode } from '@/lib/connect/instance/upstreams'
|
|
import { CONNECTOR_CAPABILITIES } from '@/lib/entitlements/keys'
|
|
import { getCompanyIdsWithCapability } from '@/lib/entitlements/has-capability'
|
|
|
|
/**
|
|
* GET /api/connector/status: the self-hosted operator's "is the connector
|
|
* wired up" view. Reports whether a connector key is configured, which
|
|
* upstreams are in connector mode (routed through the hosted proxy) vs run on
|
|
* the instance's own credentials, and, for the caller's own company, which
|
|
* connector capabilities are currently granted (written by the hourly sync).
|
|
*
|
|
* 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 } }, NO_STORE)
|
|
}
|
|
const cfg = getConnectorConfig()
|
|
const bank = bankConnectorMode()
|
|
const skv = skatteverketConnectorMode()
|
|
|
|
const grants = companyId
|
|
? await getConnectorGrantsFor(supabase, companyId)
|
|
: []
|
|
|
|
return NextResponse.json({
|
|
data: {
|
|
self_hosted: true,
|
|
configured: !!cfg,
|
|
connect_url: cfg?.baseUrl ?? null,
|
|
key_prefix: cfg ? cfg.key.slice(0, 13) : null,
|
|
upstreams: {
|
|
bank: cfg ? (bank ? 'connector' : 'own_credentials') : 'unconfigured',
|
|
skatteverket: cfg ? (skv ? 'connector' : 'own_credentials') : 'unconfigured',
|
|
},
|
|
granted_capabilities: grants,
|
|
},
|
|
}, NO_STORE)
|
|
})
|
|
|
|
async function getConnectorGrantsFor(
|
|
supabase: Parameters<typeof getCompanyIdsWithCapability>[0],
|
|
companyId: string,
|
|
): Promise<string[]> {
|
|
const held: string[] = []
|
|
for (const cap of CONNECTOR_CAPABILITIES) {
|
|
const ids = await getCompanyIdsWithCapability(supabase, [companyId], cap)
|
|
if (ids.has(companyId)) held.push(cap)
|
|
}
|
|
return held
|
|
}
|