diff --git a/docs/accessible-dialog-migration.md b/docs/accessible-dialog-migration.md new file mode 100644 index 00000000..6ef0c2dc --- /dev/null +++ b/docs/accessible-dialog-migration.md @@ -0,0 +1,50 @@ +# Accessible Dialog Migration Guide + +## Problem + +Several components use hand-rolled modal patterns (e.g. a `
` with +`position: fixed` and a manual backdrop) that lack proper ARIA semantics, +focus trapping, and keyboard dismissal. + +## Solution + +The project already has `components/ui/dialog.tsx` built on **Radix UI** +`Dialog.Root` which provides: + +- `role="dialog"` and `aria-modal="true"` automatically +- Focus trap when open +- `Escape` key dismissal +- Screen reader announcement via `DialogTitle` and `DialogDescription` + +## Migration pattern + +**Before (hand-rolled):** +```tsx +{isOpen && ( +
+
+

My Modal

+ ... +
+
+)} +``` + +**After (accessible Dialog):** +```tsx +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; + + + + + My Modal + + ... + + +``` + +## Components to migrate + +Audit `components/escrow/modals/` and `components/settings/` for any +components that render a fixed overlay manually rather than using `Dialog`. \ No newline at end of file diff --git a/docs/concurrent-session-management.md b/docs/concurrent-session-management.md new file mode 100644 index 00000000..157061c1 --- /dev/null +++ b/docs/concurrent-session-management.md @@ -0,0 +1,36 @@ +# Concurrent Session Management + +## Overview + +This document describes the design for limiting concurrent sessions per user +and revoking tokens when the session limit is exceeded. + +## Design + +### Session limit + +Each user may have at most **5** active sessions simultaneously. When a new +login exceeds this limit the oldest refresh token is revoked automatically. + +### Token revocation flow + +1. User logs in → `AuthService.verifySignature` creates a refresh token via `UserService.createRefreshToken`. +2. Before saving, query active tokens for the user ordered by `createdAt ASC`. +3. If `count >= MAX_SESSIONS`, call `UserService.invalidateRefreshToken` on the oldest token. +4. Proceed with saving the new token and returning the access/refresh pair. + +### Key constants + +```ts +const MAX_SESSIONS = 5; +``` + +### API endpoint + +`POST /v1/auth/logout-all` — invalidates **all** active refresh tokens for the +authenticated user. Requires a valid `Authorization: Bearer ` header. + +## Future work + +- Emit a `session.revoked` event so connected WebSocket clients can notify the user. +- Add a `deviceInfo` field to `RefreshToken` to display session metadata in the UI. \ No newline at end of file diff --git a/docs/database-constraints-audit.md b/docs/database-constraints-audit.md new file mode 100644 index 00000000..9ac69cbf --- /dev/null +++ b/docs/database-constraints-audit.md @@ -0,0 +1,38 @@ +# Database Constraints Audit + +## Purpose + +Ensure all TypeORM entities have appropriate database-level constraints +to prevent invalid data from entering the system. + +## Findings & Recommendations + +### `users` table + +| Column | Current | Recommended | +|---|---|---| +| `walletAddress` | `UNIQUE` | ✅ OK | +| `email` | `UNIQUE, nullable` | ✅ OK | +| `displayName` | `varchar(100), nullable` | ✅ OK | +| `role` | `text enum` | Add `CHECK` via migration | + +### `refresh_tokens` table + +| Column | Current | Recommended | +|---|---|---| +| `token` | no index | Add `UNIQUE` constraint | +| `expiresAt` | no constraint | Add `CHECK (expires_at > created_at)` | + +### `escrow` entities + +- Add `CHECK` constraint: `amount > 0` +- Add `NOT NULL` on `status` column where currently missing + +## Migration Plan + +Generate a migration after applying the entity changes: + +```bash +npm run migration:generate -- --name AddMissingConstraints -d src/data-source.ts +npm run migration:run +``` \ No newline at end of file diff --git a/docs/graceful-shutdown.md b/docs/graceful-shutdown.md new file mode 100644 index 00000000..05099519 --- /dev/null +++ b/docs/graceful-shutdown.md @@ -0,0 +1,44 @@ +# Graceful Shutdown Handler + +## Overview + +NestJS provides built-in lifecycle hooks for graceful shutdown. +This document describes how to enable them in `main.ts` and implement +`OnModuleDestroy` in long-running services. + +## Enabling shutdown hooks in main.ts + +Add the following line after `NestFactory.create`: + +```ts +app.enableShutdownHooks(); +``` + +This ensures NestJS listens for `SIGTERM` / `SIGINT` and calls `onModuleDestroy` +on every provider that implements it before the process exits. + +## Implementing OnModuleDestroy + +For services that hold open connections (DB, Redis, WebSocket, scheduled jobs): + +```ts +import { Injectable, OnModuleDestroy } from '@nestjs/common'; + +@Injectable() +export class SomeService implements OnModuleDestroy { + onModuleDestroy() { + // close connections, flush buffers, cancel scheduled jobs + } +} +``` + +## Services that need this + +- `SchedulerService` — cancel cron jobs +- `StellarService` — close streaming connections +- `GatewayService` — close WebSocket server gracefully + +## Kubernetes / Docker + +Set `terminationGracePeriodSeconds: 30` in the pod spec so the container +has enough time to finish in-flight requests before being killed. \ No newline at end of file