From 86ec8966d0ca0cd0701770f74604ce0670782421 Mon Sep 17 00:00:00 2001 From: Schalli Date: Sat, 27 Jun 2026 00:00:14 +0200 Subject: [PATCH] feat(07-02): InboxProvider interface + config/vehicle/history DTOs - inbox-provider.interface.ts: InboxProvider contract with fetchPdfAttachments + testConnection; re-exports InboxConfig/InboxEmail/InboxAttachment with export type (isolatedModules) - dkv-config.dto.ts: DkvConfigDto with @IsEmail() senderFilter/exportRecipient, @Min(5) pollIntervalMin, @IsIn() protocol/encryption (T-07-04) - dkv-vehicle.dto.ts: CreateVehicleDto (all required @IsNotEmpty) + UpdateVehicleDto (all optional) - dkv-history.dto.ts: DkvHistoryQueryDto with @IsInt @Min(1) page/limit pagination (T-07-06) - Fix: export type re-exports required for isolatedModules TypeScript setting --- apps/api/src/dkv/dto/dkv-config.dto.ts | 112 ++++++++++++++++++ apps/api/src/dkv/dto/dkv-history.dto.ts | 30 +++++ apps/api/src/dkv/dto/dkv-vehicle.dto.ts | 74 ++++++++++++ .../dkv/providers/inbox-provider.interface.ts | 32 +++++ 4 files changed, 248 insertions(+) create mode 100644 apps/api/src/dkv/dto/dkv-config.dto.ts create mode 100644 apps/api/src/dkv/dto/dkv-history.dto.ts create mode 100644 apps/api/src/dkv/dto/dkv-vehicle.dto.ts create mode 100644 apps/api/src/dkv/providers/inbox-provider.interface.ts diff --git a/apps/api/src/dkv/dto/dkv-config.dto.ts b/apps/api/src/dkv/dto/dkv-config.dto.ts new file mode 100644 index 0000000..06eb381 --- /dev/null +++ b/apps/api/src/dkv/dto/dkv-config.dto.ts @@ -0,0 +1,112 @@ +import { + IsBoolean, + IsEmail, + IsIn, + IsInt, + IsOptional, + IsString, + Max, + Min, +} from 'class-validator'; + +/** + * DTO for creating or updating DKV module configuration. + * + * Security: + * - T-07-04: senderFilter validated as email address (injection mitigation) + * - T-07-04: port constrained to 1–65535 (invalid port rejection) + * - T-07-04: protocol and encryption constrained with @IsIn (enum injection mitigation) + * - T-07-05: pollIntervalMin minimum 5 (DoS mitigation — no sub-5-minute polling) + */ +export class DkvConfigDto { + /** + * Inbox protocol — 'imap' for IMAP, 'exchange' for Exchange (EWS). + */ + @IsIn(['imap', 'exchange']) + protocol!: string; + + /** + * Mail server hostname or IP address (IMAP: IMAP server, Exchange: EWS endpoint host). + */ + @IsOptional() + @IsString() + host?: string; + + /** + * TCP port. Standard values: 993 (IMAP SSL/TLS), 143 (IMAP STARTTLS), 443 (EWS). + */ + @IsOptional() + @IsInt() + @Min(1) + @Max(65535) + port?: number; + + /** + * TLS mode: 'none' = plain TCP, 'starttls' = STARTTLS upgrade, 'ssl-tls' = implicit TLS. + */ + @IsIn(['none', 'starttls', 'ssl-tls']) + encryption!: string; + + /** + * IMAP folder to monitor (e.g. "INBOX", "DKV"). Exchange always uses Inbox. + */ + @IsOptional() + @IsString() + folder?: string; + + /** + * Sender email address to filter by. Only emails from this sender are processed. + * Validated as email address to prevent header injection (T-07-04 / Research V5). + */ + @IsOptional() + @IsEmail() + senderFilter?: string; + + /** + * Email address to which the generated Excel export is sent. + * Validated as email address (T-07-04). + */ + @IsOptional() + @IsEmail() + exportRecipient?: string; + + /** + * Polling interval in minutes. Minimum 5 minutes to prevent DoS via excessive polling + * (T-07-05, UI-SPEC minimum = 5). + */ + @IsOptional() + @IsInt() + @Min(5) + pollIntervalMin?: number; + + /** + * Whether the automatic polling cron job is active. + */ + @IsOptional() + @IsBoolean() + isActive?: boolean; + + /** + * Format string for the Fahrzeug column in the Excel export. + * Placeholders: {Marke}, {Modell}, {Kennzeichen}, {Fahrer}. + * Default: "{Marke}/{Modell}/{Kennzeichen}". + */ + @IsOptional() + @IsString() + vehicleFormatString?: string; + + /** + * Inbox username (stored encrypted; cleared after save). + */ + @IsOptional() + @IsString() + username?: string; + + /** + * Inbox password (stored encrypted; cleared after save). + * T-07-03: never returned to the frontend in responses. + */ + @IsOptional() + @IsString() + password?: string; +} diff --git a/apps/api/src/dkv/dto/dkv-history.dto.ts b/apps/api/src/dkv/dto/dkv-history.dto.ts new file mode 100644 index 0000000..d441f93 --- /dev/null +++ b/apps/api/src/dkv/dto/dkv-history.dto.ts @@ -0,0 +1,30 @@ +import { IsInt, IsOptional, Min } from 'class-validator'; + +/** + * Query DTO for paginating the DKV invoice processing history. + * + * Security: + * - T-07-06: Pagination limits prevent unbounded history accumulation DoS + * (Research Security Domain: "Excessive history accumulation" pattern) + * + * Used for: GET /dkv/history?page=1&limit=20 + */ +export class DkvHistoryQueryDto { + /** + * Page number (1-based). Defaults to 1 when omitted. + * T-07-06: bounded integer prevents negative-page or non-integer injection. + */ + @IsOptional() + @IsInt() + @Min(1) + page?: number; + + /** + * Number of records per page. Defaults to 20 when omitted. + * T-07-06: bounded integer mitigates oversized result-set DoS. + */ + @IsOptional() + @IsInt() + @Min(1) + limit?: number; +} diff --git a/apps/api/src/dkv/dto/dkv-vehicle.dto.ts b/apps/api/src/dkv/dto/dkv-vehicle.dto.ts new file mode 100644 index 0000000..15468a1 --- /dev/null +++ b/apps/api/src/dkv/dto/dkv-vehicle.dto.ts @@ -0,0 +1,74 @@ +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +/** + * DTO for creating a vehicle master entry. + * + * All four fields represent the vehicle identity and driver mapping. + * Used for: POST /dkv/vehicles + */ +export class CreateVehicleDto { + /** + * License plate (Kennzeichen), e.g. "GP-JL 728E". + * Unique per tenant — used to map DKV invoice vehicle blocks to master data. + */ + @IsString() + @IsNotEmpty() + kennzeichen!: string; + + /** + * Vehicle make (Marke), e.g. "Mercedes". + */ + @IsString() + @IsNotEmpty() + marke!: string; + + /** + * Vehicle model (Modell), e.g. "GLC 300 de 4MATIC". + */ + @IsString() + @IsNotEmpty() + modell!: string; + + /** + * Driver name in "Vorname Nachname" format, e.g. "Max Mustermann". + * Mapped to the Fahrer column in the Excel export (D-13). + */ + @IsString() + @IsNotEmpty() + fahrer!: string; +} + +/** + * DTO for updating a vehicle master entry (all fields optional). + * + * Used for: PATCH /dkv/vehicles/:id + */ +export class UpdateVehicleDto { + /** + * New license plate. Updating changes the lookup key — use with care. + */ + @IsOptional() + @IsString() + kennzeichen?: string; + + /** + * New vehicle make. + */ + @IsOptional() + @IsString() + marke?: string; + + /** + * New vehicle model. + */ + @IsOptional() + @IsString() + modell?: string; + + /** + * New driver name. + */ + @IsOptional() + @IsString() + fahrer?: string; +} diff --git a/apps/api/src/dkv/providers/inbox-provider.interface.ts b/apps/api/src/dkv/providers/inbox-provider.interface.ts new file mode 100644 index 0000000..4a14a5c --- /dev/null +++ b/apps/api/src/dkv/providers/inbox-provider.interface.ts @@ -0,0 +1,32 @@ +import { InboxAttachment, InboxConfig, InboxEmail } from '../dkv.types'; + +// Re-export types for downstream consumers that import from this module +export type { InboxAttachment, InboxConfig, InboxEmail }; + +/** + * Abstract inbox provider contract — implemented by ImapProvider and + * ExchangeInboxProvider. Consumers (DkvService, DkvSchedulerService) depend + * only on this interface, not on the concrete implementations. + * + * Security: + * - T-07-03: Implementations MUST NOT log credential values + * - InboxConfig carries decrypted credentials — never persist, never serialize + */ +export interface InboxProvider { + /** + * Connects to the configured inbox, searches for emails from the configured + * sender filter, downloads PDF attachments, and returns all matching emails. + * + * @param config Decrypted inbox connection parameters + * @returns Array of emails with their PDF attachments as Buffers + */ + fetchPdfAttachments(config: InboxConfig): Promise; + + /** + * Tests whether the inbox connection can be established. + * + * @param config Decrypted inbox connection parameters + * @returns true if connection succeeded, false on any auth/network error + */ + testConnection(config: InboxConfig): Promise; +}