Märkesspecifika kopplingar — kunden lägger in sina egna credentials

Verkstaden har redan sina avtal: Volvo-verkstaden har VIDA, VAG-verkstaden
har erWin, den fria verkstaden har en fordonsdataleverantör. Kopplingarna
konfigureras därför av kunden själv under Inställningar, med sina egna
uppgifter — vi tillhandahåller ramen, inte kontot.

Uppgifterna når aldrig webbläsaren. De krypteras med AES-256-GCM
(INTEGRATION_NYCKEL) innan de skrivs till tabellen integrationer, och
API:t returnerar hemliga fält maskerade. Alla uppslag görs av servern.
Saknas krypteringsnyckeln sparas ingenting alls — 503 och en förklaring i
gränssnittet i stället för klartext i databasen. Endast
systemadministratören hanterar uppgifterna; kopplingarna är
organisationsknutna som all annan ärendedata.

Leverantörer är data, inte kod: URL-mall, autentiseringstyp
(bearer/header/basic/query) och svarsmappning beskrivs i
services/plattform/integrationer.json, utbytbar via ConfigMap
(INTEGRATIONER_FIL). Nya märken läggs till utan att appen byggs om.

Varje uppslag skriver senast_testad och senaste_status på kopplingen, så
ett utgånget abonnemang syns i inställningarna i stället för att ge tysta
tomma svar.

Två latenta krascher hittade av klicktestet och åtgärdade: TextFalt och
UNDANTAGSORSAKER användes utan import. vite build typkontrollerar inte,
så de passerade bygget — därav nya npm-skriptet typkontroll, nu del av
verifieringen.

Verifierat: 80 vitest-tester, typkontroll, eslint, OpenAPI-validering,
integrationstest mot riktig Postgres (rollstyrning, kryptering i vila,
maskering, organisationsisolering, fail closed, borttagning) och
klickgenomgång mot en körande plattform.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-03 12:50:44 +00:00
parent 64b2124cd8
commit 3a0be7261e
18 changed files with 1112 additions and 21 deletions
+6 -1
View File
@@ -3,7 +3,8 @@
# kubectl -n guidad-felsokning create secret generic felsokning-hemligheter \
# --from-literal=anthropic-api-key='sk-ant-…' \
# --from-literal=jwt-secret="$(openssl rand -base64 48)" \
# --from-literal=postgres-losenord="$(openssl rand -base64 24)"
# --from-literal=postgres-losenord="$(openssl rand -base64 24)" \
# --from-literal=integration-nyckel="$(openssl rand -hex 32)"
#
# I produktion: använd extern secrets-hantering (External Secrets Operator,
# Sealed Secrets eller molnleverantörens secret manager).
@@ -17,3 +18,7 @@ stringData:
anthropic-api-key: ERSATT-MIG
jwt-secret: ERSATT-MIG
postgres-losenord: ERSATT-MIG
# 32 byte hex (openssl rand -hex 32) — krypterar kundernas
# märkesspecifika credentials i vila. Byts nyckeln måste kopplingarna
# sparas om; tjänsten visar då inga värden i stället för att gissa.
integration-nyckel: ERSATT-MIG
+6
View File
@@ -24,6 +24,12 @@ spec:
- name: JWT_SECRET
valueFrom:
secretKeyRef: { name: felsokning-hemligheter, key: jwt-secret }
# Krypteringsnyckel för kundernas märkesspecifika credentials
# (32 byte hex eller base64). Saknas den sparas inga uppgifter
# alls — tjänsten failar closed i stället för att lagra i klartext.
- name: INTEGRATION_NYCKEL
valueFrom:
secretKeyRef: { name: felsokning-hemligheter, key: integration-nyckel }
- name: POSTGRES_LOSENORD
valueFrom:
secretKeyRef: { name: felsokning-hemligheter, key: postgres-losenord }
+15
View File
@@ -16,6 +16,21 @@ create table if not exists organisationer (
);
alter table organisationer add column if not exists installningar jsonb not null default '{}'::jsonb;
-- Märkesspecifika kopplingar per organisation. Uppgifterna lagras
-- krypterade (AES-256-GCM, nyckel ur INTEGRATION_NYCKEL) och lämnar
-- aldrig servern i klartext — klienten ser bara maskerade värden.
create table if not exists integrationer (
organisation_id uuid not null references organisationer(id),
leverantor text not null,
uppgifter_krypt text not null,
aktiv boolean not null default true,
skapad timestamptz not null default now(),
uppdaterad timestamptz not null default now(),
senast_testad timestamptz,
senaste_status text,
primary key (organisation_id, leverantor)
);
create table if not exists anvandare (
id uuid primary key default gen_random_uuid(),
organisation_id uuid not null references organisationer(id),