Skip to content

Repository files navigation

bank-api

CI tests coverage

English summary. REST API of a banking application, built as a university project for two courses: Web Applications, and Information Systems in Banking. NestJS 11 + Prisma 6 + PostgreSQL (Supabase) + Redis, TypeScript in strict mode, running on Bun.

Authentication is implemented from scratch instead of delegating to a provider: JWT access/refresh pair, Argon2id password hashing, TOTP two-factor, brute-force lockout, and AES-256-GCM for sensitive secrets at rest. The banking domain covers accounts, transfers with idempotency keys, cards, loans with scoring and an instalment schedule, deposits with interest, a KYC gate, an audit log, PDF statements and an admin area.

Testing: 83 unit tests over the financial logic and crypto, plus an 18-step end-to-end journey that runs against a disposable Postgres + Redis pair in Docker and walks the full path — registration, KYC gate rejecting a transfer, admin approval, top-up, transfer, idempotency replay, insufficient funds, and RBAC. Design notes are in docs/ARCHITECTURE.md and docs/THREAT-MODEL.md.

The rest of this document is in Polish.

REST API aplikacji bankowej — backend projektu zaliczeniowego (Aplikacje Webowe + Systemy Informatyczne w Bankowości).

Testy: bun run test (unit, 83 testy) · bun run test:cov (coverage) · bun run test:e2e (18 testów, pełna ścieżka HTTP). Logika finansowa (scoring, harmonogram rat, odsetki, IBAN/PESEL/Luhn) i krypto (AES-GCM, Argon2, TOTP) pokryte testami jednostkowymi — szczegóły w docs/ARCHITECTURE.md i docs/THREAT-MODEL.md.

Testy e2e (zautomatyzowany scenariusz "manualny")

bun run test:e2e jedną komendą: podnosi izolowany Postgres+Redis (docker-compose.test.yml, porty 5433/6380 — nie dotyka Supabase), migruje, seeduje, odpala Jest i zawsze sprząta kontenery. Scenariusz test/journey.e2e-spec.ts przechodzi całą ścieżkę bankową przez prawdziwe HTTP API:

admin login → rejestracja klienta → login → KYC GATE blokuje przelew (403)
→ submit KYC → admin approve → konto ACTIVE → admin doładowuje konto
→ przelew wewnętrzny → weryfikacja sald → IDEMPOTENCJA → brak środków (400)
→ RBAC (klient ≠ admin, 403) → brak tokenu (401)

Gdy kontenery testowe już działają, sam Jest: bun run test:e2e:jest (start/stop ręcznie: bun run test:e2e:db:up / test:e2e:db:down).

Stack

  • Runtime / PM: Bun 1.3+
  • Framework: NestJS 11 (TypeScript strict, decorators)
  • ORM: Prisma 6 (z Prisma.Decimal dla pieniędzy)
  • DB: PostgreSQL (managed przez Supabase) — connection pooling (PgBouncer)
  • Cache / sesje: Redis 7 (ioredis)
  • Auth: własny — JWT access/refresh + Argon2id + 2FA TOTP
  • Walidacja: class-validator + class-transformer (DTO) + zod (env)
  • Docs: OpenAPI/Swagger pod /docs (dev only)
  • Container: Docker (multi-stage, oven/bun:1.3-alpine)

Wymagania

  • Bun ≥ 1.3
  • Docker + Docker Compose (na Redisa lokalnego)
  • Projekt Supabase z DATABASE_URL (pooler:6543) i DIRECT_URL (direct:5432)

Setup

# 1. Zależności
bun install

# 2. Środowisko
cp .env.example .env
# Uzupełnij wszystkie sekrety:
#   openssl rand -base64 64  → JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, TWO_FACTOR_TOKEN_SECRET
#   openssl rand -base64 32  → ENCRYPTION_KEY

# 3. Migracja Supabase + Prisma Client
bun run prisma:migrate:dev --name init

# 4. Seed: produkty lokat + pierwszy admin
#    (wymaga ADMIN_EMAIL + ADMIN_PASSWORD w .env)
bun run db:seed

# 5. Redis lokalnie
docker compose up redis -d

# 6. Dev server
bun run start:dev

API: http://localhost:3001/api/v1. Swagger: http://localhost:3001/docs.

Skrypty

Komenda Opis
bun run start:dev Hot-reload dev server
bun run start:prod Production server (po build)
bun run build Kompilacja do dist/
bun run lint ESLint --fix
bun run typecheck tsc --noEmit
bun run test Unit testy (jest)
bun run test:e2e E2E testy
bun run prisma:migrate:dev Migracja w trybie dev
bun run prisma:migrate:deploy Migracje na produkcji (z .prisma/migrations/)
bun run prisma:studio Prisma Studio (GUI do bazy)
bun run db:seed Seed produktów lokat + admina

Docker

docker compose up --build

Postgres NIE jest w docker-compose.yml — używamy zewnętrznego Supabase. W compose stoi tylko Redis + API.

Struktura

src/
├── accounts/        # rachunki (CRUD, IBAN PL gen+validate, status)
├── admin/           # AdminController — lista użytkowników, suspend/activate/promote, stats
├── audit/           # AuditService — best-effort logger
├── auth/            # JWT + Argon2 + 2FA TOTP + brute-force lockout
├── cards/           # karty DEBIT (Luhn + AES-GCM + Argon2 PIN + reveal)
├── common/
│   ├── filters/     # global exception filter z mapping Prisma errors
│   ├── services/    # CryptoService (AES-256-GCM + SHA-256 + random)
│   └── utils/       # iban, pesel, card-number, loan-schedule, loan-scoring, deposit-interest
├── config/          # walidacja env (zod)
├── deposits/        # produkty lokat + Deposit lifecycle (open/close/break)
├── kyc/             # KycSubmission z review queue
├── loans/           # LoanApplication (scoring) + Loan + Installment (annuity/decreasing)
├── prisma/          # PrismaService (singleton)
├── redis/           # RedisService + module (globalny)
├── transfers/       # internal/external transfers, Serializable isolation
├── users/           # minimalny UsersService (lookup + toSafe)
├── app.module.ts
└── main.ts          # bootstrap: helmet + cors + Swagger + global filter/pipe
prisma/
├── schema.prisma
└── seed.ts          # produkty lokat + admin (idempotentny)

Endpoints (skrót)

Auth (/api/v1/auth)

  • POST /register, POST /login, POST /login/2fa, POST /refresh, POST /logout
  • GET /me — profil zalogowanego
  • POST /2fa/setup, POST /2fa/confirm, POST /2fa/disable

KYC (/api/v1/kyc)

  • Klient: POST /submit, GET /me
  • Admin: GET /pending, GET /:id, POST /:id/approve, POST /:id/reject

Rachunki (/api/v1/accounts)

  • GET /, GET /:id, POST / (SAVINGS), PATCH /:id, DELETE /:id

Przelewy (/api/v1/transfers)

  • POST / (auto internal/external po IBAN, idempotency)
  • GET / (paginacja + filtry: accountId, type, status, dateRange)
  • GET /:id

Karty (/api/v1/cards)

  • POST / (wydanie + zwrot plain PAN/CVV jednorazowo), GET /, GET /:id
  • POST /:id/block, POST /:id/unblock
  • PATCH /:id/limits, POST /:id/change-pin
  • POST /:id/reveal-details (wymaga hasła)
  • DELETE /:id (zamknięcie)

Kredyty (/api/v1/loans)

  • Klient: POST /applications, GET /applications, GET /applications/:id, DELETE /applications/:id
  • GET /, GET /:id, POST /:id/installments/:installmentId/pay
  • Admin: GET /admin/applications/pending, GET /admin/applications/:id, POST /admin/applications/:id/approve|reject|disburse

Lokaty (/api/v1/deposits)

  • GET /products, GET /products/:id
  • GET /, GET /:id, POST /, POST /:id/close, POST /:id/break
  • Admin: GET /admin/products, POST /admin/products, POST /admin/products/:id/retire

Admin (/api/v1/admin)

  • GET /stats
  • GET /users, GET /users/:id
  • POST /users/:id/suspend|activate|promote

Domain rules — pamiętaj o tym

  • Decimal arithmetic: wszystkie kwoty to Prisma.Decimal(19, 4). NIGDY Float / Number do operacji na pieniądzach.
  • Serializable isolation dla każdej operacji modyfikującej saldo (transfer, disbursement, loan repayment, deposit open/close/break).
  • Idempotency: każda mutacja finansowa wymaga idempotencyKey (UUID v4) — Transaction.idempotencyKey ma unique constraint.
  • KYC gate: User.status musi być ACTIVE dla przelewów, kart, kredytów, lokat. Przed approve KYC klient jest PENDING_VERIFICATION.
  • Audit log: każda akcja wrażliwa zapisuje wpis przez AuditService.log() (best-effort, nie blokuje requestu).
  • PESEL: surowy PESEL NIGDY nie trafia do bazy — tylko sha256 hash + ostatnie 4 cyfry.
  • PAN kart i CVV: szyfrowane AES-256-GCM. Reveal wymaga ponownej autoryzacji hasłem klienta.

Bezpieczeństwo (production-ready checklist)

  • Helmet + CORS allowlist
  • Rate limiting (Throttler) + dodatkowe limity per endpoint krytyczny
  • Walidacja env przez zod (fail-fast przy starcie)
  • Whitelist + forbidNonWhitelisted w ValidationPipe
  • Argon2id dla haseł + PIN-ów kart
  • AES-256-GCM dla 2FA seed, PAN kart, CVV
  • JWT access (15min) + refresh (7d) z rotacją + replay detection
  • 2FA TOTP (otplib + QR przez qrcode)
  • Brute-force lockout (failedLoginCount + lockedUntil w User)
  • Audit log dla wszystkich akcji wrażliwych
  • Decimal(19,4) dla wszystkich kwot
  • Idempotency keys dla wszystkich mutacji finansowych
  • Serializable isolation dla operacji finansowych
  • CI: lint + typecheck + build + Docker + CodeQL
  • Penetration test — TODO przed produkcją

About

Bank API — NestJS + Prisma + Supabase Postgres, własny auth (JWT + Argon2id + 2FA TOTP). Projekt zaliczeniowy AW + SIB.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages