sojorn/sojorn_app/lib/utils/request_signing.dart
2026-02-01 16:06:12 -06:00

98 lines
2.6 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
/// Request signing utility for critical API operations
class RequestSigning {
/// Generate HMAC-SHA256 signature for request
static String signRequest(
String method,
String path,
Map<String, dynamic>? body,
String timestamp,
String secretKey,
) {
// Create canonical request
final canonicalRequest = _buildCanonicalRequest(method, path, body, timestamp);
// Generate HMAC-SHA256 signature
final key = utf8.encode(secretKey);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(utf8.encode(canonicalRequest));
return digest.toString();
}
/// Build canonical request string for signing
static String _buildCanonicalRequest(
String method,
String path,
Map<String, dynamic>? body,
String timestamp,
) {
final parts = [
method.toUpperCase(),
path,
timestamp,
];
if (body != null && body.isNotEmpty) {
final sortedBody = Map<String, dynamic>.fromEntries(
body.entries.toList()..sort((a, b) => a.key.compareTo(b.key))
);
parts.add(jsonEncode(sortedBody));
}
return parts.join('\n');
}
/// Verify request signature
static bool verifySignature(
String method,
String path,
Map<String, dynamic>? body,
String timestamp,
String signature,
String secretKey,
) {
final expectedSignature = signRequest(method, path, body, timestamp, secretKey);
return _constantTimeEquals(signature, expectedSignature);
}
/// Constant-time comparison to prevent timing attacks
static bool _constantTimeEquals(String a, String b) {
if (a.length != b.length) return false;
int result = 0;
for (int i = 0; i < a.length; i++) {
result |= a.codeUnitAt(i) ^ b.codeUnitAt(i);
}
return result == 0;
}
/// Generate timestamp for request signing
static String generateTimestamp() {
return DateTime.now().toUtc().toIso8601String().replaceAll('.', '').replaceAll('Z', '');
}
/// Add signature headers to request
static Map<String, String> addSignatureHeaders(
Map<String, String> headers,
String method,
String path,
Map<String, dynamic>? body,
String secretKey,
) {
final timestamp = generateTimestamp();
final signature = signRequest(method, path, body, timestamp, secretKey);
final signedHeaders = Map<String, String>.from(headers);
signedHeaders['X-Timestamp'] = timestamp;
signedHeaders['X-Signature'] = signature;
signedHeaders['X-Algorithm'] = 'HMAC-SHA256';
return signedHeaders;
}
}