Skip to main content

Development Rules

Development Rules

Seven mandatory rules for all Tok development. These rules exist because financial data and PSD2 compliance leave no room for shortcuts.


Never update or delete a consent record. Each state change creates a new log entry:


Rule 2 — Token Encryption Mandatory

AES-256-GCM + GCP Cloud KMS for ALL OAuth tokens. No exceptions.

CORRECT:  Store tokens via Cloud KMS envelope encryption → encrypted_dek + iv + ciphertext in DB
WRONG:    Store raw tokens in DB or env vars
WRONG:    Store tokens in logs, files, or memory beyond request lifecycle

QWAC private key must also live in GCP Cloud KMS HSM — signing is done via Cloud KMS API, the key is never extracted.


Rule 3 — Bank Adapter Pattern

Every bank integration goes through the abstract BankAdapter interface.

// Correct
class BerlinGroupAdapter : BankAdapter { ... }
class BilateralAdapter   : BankAdapter { ... }

// Wrong — never call bank HTTP endpoints directly from services/routes

The adapter is the only layer that knows about bank-specific API formats. Services above it work only with normalized BankTransaction objects.


Rule 4 — Deduplication via externalId

externalId (bank's own transaction ID) + bankAccountId = unique constraint.

Duplicate imports are silently skipped — this is intentional, not an error. Never create duplicate transactions.

ALTER TABLE bank_transactions
ADD CONSTRAINT uq_bank_account_external
UNIQUE (bank_account_id, external_id);

Rule 5 — Money = NUMERIC(19,4)

Never use float or double for financial amounts.

// Correct
val amount: BigDecimal  // Kotlin
// Correct in DB
amount NUMERIC(19,4)

// Wrong
val amount: Double  // loses precision
val amount: Float   // loses precision

All amounts from bank APIs must be parsed to BigDecimal before storage. Amount equality comparisons use exact decimal matching.


Rule 6 — CSRF on Consent

// Correct
val state = java.security.SecureRandom()
    .generateSeed(32)
    .toHexString()
// Store in server-side session (NOT cookie, NOT localStorage)
// Validate on callback — reject if mismatch
// One-time use — invalidate after successful exchange

// Wrong
val state = UUID.randomUUID().toString()  // too predictable
val state = "fixed-string"               // completely insecure

Rule 7 — 90-Day Consent Tracking

Every BankConnection must have automated expiry monitoring.

PSD2 (EBA RTS Art. 10) requires re-authentication every 90 days.

Mandatory implementation:

  • consentValidUntil field on every BankConnection
  • Daily cron: check all active connections
  • 14 days before expiry: send email to org admin
  • On expiry: set consentStatus = 'expired', pause sync jobs
  • UI: show "Bank feed paused — click to reconnect"
  • One-click re-connect flow (user re-does SCA, new tokens stored)

Without this, bank feed silently breaks for ALL users when consents expire simultaneously.


Summary

Rule Enforcement
1. Consent immutability Code review — no UPDATE/DELETE on consent tables
2. Token encryption No raw token strings in code/DB/logs
3. Bank adapter pattern No direct HTTP bank calls outside adapter layer
4. Deduplication DB unique constraint enforced
5. Money = NUMERIC(19,4) No float/double for amounts anywhere
6. CSRF on consent State parameter required in every consent initiation
7. 90-day tracking Daily cron + email notification mandatory