LCOV - code coverage report
Current view: top level - src/workers - request_signing.dart Coverage Total Hit
Test: lcov.info Lines: 100.0 % 8 8
Test Date: 2026-04-30 18:23:23 Functions: - 0 0

            Line data    Source code
       1              : import 'package:flutter/foundation.dart';
       2              : 
       3              : /// HMAC-based request signing configuration for HTTP workers.
       4              : ///
       5              : /// When set on an HTTP worker, each request is signed using HMAC-SHA256 and
       6              : /// the signature is added as a request header (default: `X-Signature`).
       7              : /// An optional `X-Timestamp` header is also added (in milliseconds since epoch)
       8              : /// so that servers can reject replayed requests that are too old.
       9              : ///
      10              : /// ## What gets signed
      11              : ///
      12              : /// The signature covers a canonical message composed of:
      13              : /// ```
      14              : /// METHOD\n
      15              : /// URL\n
      16              : /// BODY\n          ← only when signBody=true and there is a body
      17              : /// TIMESTAMP       ← only when includeTimestamp=true
      18              : /// ```
      19              : ///
      20              : /// ## Example — download with server validation
      21              : ///
      22              : /// ```dart
      23              : /// worker: HttpDownloadWorker(
      24              : ///   url: 'https://api.example.com/protected/report.pdf',
      25              : ///   savePath: '/tmp/report.pdf',
      26              : ///   requestSigning: RequestSigning(
      27              : ///     secretKey: env['API_SECRET']!,
      28              : ///   ),
      29              : /// ),
      30              : /// ```
      31              : ///
      32              : /// ## Example — upload with non-default header name
      33              : ///
      34              : /// ```dart
      35              : /// worker: HttpUploadWorker(
      36              : ///   url: 'https://api.example.com/upload',
      37              : ///   filePath: '/tmp/data.csv',
      38              : ///   requestSigning: RequestSigning(
      39              : ///     secretKey: env['API_SECRET']!,
      40              : ///     headerName: 'X-Hub-Signature-256',
      41              : ///     signaturePrefix: 'sha256=',
      42              : ///   ),
      43              : /// ),
      44              : /// ```
      45              : ///
      46              : /// ## Server-side verification (example in Node.js)
      47              : ///
      48              : /// ```js
      49              : /// const crypto = require('crypto');
      50              : /// function verify(req, secret) {
      51              : ///   const ts    = req.headers['x-timestamp'] ?? '';
      52              : ///   const sig   = req.headers['x-signature']  ?? '';
      53              : ///   const body  = req.rawBody ?? '';
      54              : ///   const msg   = `${req.method}\n${req.url}\n${body}\n${ts}`;
      55              : ///   const expected = crypto.createHmac('sha256', secret).update(msg).digest('hex');
      56              : ///   return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
      57              : /// }
      58              : /// ```
      59              : @immutable
      60              : class RequestSigning {
      61            1 :   const RequestSigning({
      62              :     required this.secretKey,
      63              :     this.headerName = 'X-Signature',
      64              :     this.signaturePrefix = '',
      65              :     this.includeTimestamp = true,
      66              :     this.signBody = true,
      67            3 :   }) : assert(secretKey.length >= 16,
      68              :             'secretKey must be at least 16 characters for meaningful security');
      69              : 
      70              :   /// The shared secret used to compute the HMAC-SHA256 digest.
      71              :   ///
      72              :   /// Must be at least 16 characters. Keep this out of source control —
      73              :   /// read it from a secure secret store at runtime.
      74              :   final String secretKey;
      75              : 
      76              :   /// Name of the HTTP header that carries the signature.
      77              :   ///
      78              :   /// Default: `'X-Signature'`
      79              :   final String headerName;
      80              : 
      81              :   /// Optional string prepended to the raw hex digest in [headerName].
      82              :   ///
      83              :   /// GitHub-style webhooks use `'sha256='`. Leave empty for a bare hex string.
      84              :   ///
      85              :   /// Default: `''` (bare hex)
      86              :   final String signaturePrefix;
      87              : 
      88              :   /// When `true` (default), the current Unix timestamp in **milliseconds** is
      89              :   /// included in the signed message and sent as an `X-Timestamp` header.
      90              :   ///
      91              :   /// Servers should reject requests whose `X-Timestamp` is more than
      92              :   /// a configurable window (e.g. 5 minutes) in the past to prevent replay attacks.
      93              :   final bool includeTimestamp;
      94              : 
      95              :   /// When `true` (default), the request body bytes are included in the signed
      96              :   /// message.
      97              :   ///
      98              :   /// For GET/HEAD requests (no body), this has no effect.
      99              :   /// For large upload bodies this adds a small CPU cost.
     100              :   final bool signBody;
     101              : 
     102            4 :   Map<String, dynamic> toMap() => {
     103            2 :         'secretKey': secretKey,
     104            2 :         'headerName': headerName,
     105            2 :         'signaturePrefix': signaturePrefix,
     106            2 :         'includeTimestamp': includeTimestamp,
     107            2 :         'signBody': signBody,
     108              :       };
     109              : }
        

Generated by: LCOV version 2.4-0