import 'dart:convert'; import 'dart:typed_data'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:waylume_server/core/utils.dart'; import '../config/supabase_config.dart'; class RollingCodesService { // Shared constants (hardcoded to match edge function constants) static const String registrationSeed = 'waylume_rolling_registration_seed_2025'; static const String secretSalt = 'waylume_operational_secret_salt_2025'; static const int timeWindowSeconds = 300; // 5 minutes static String? _operationalSeed; static String? _serverId; /// Initialize service static Future initialize() async { _serverId = fromEnivronment('SERVER_ID'); if (_serverId == null) { throw Exception('SERVER_ID environment variable is required'); } // No need to load configuration - server re-registers on every restart print('Rolling codes service initialized for server $_serverId'); } /// Generate registration rolling code static String generateRegistrationCode([DateTime? timestamp]) { timestamp ??= DateTime.now(); final timeWindow = timestamp.millisecondsSinceEpoch ~/ 1000 ~/ timeWindowSeconds; return _generateHmacSha256(registrationSeed, timeWindow.toString()); } /// Generate operational rolling code static String generateOperationalCode([DateTime? timestamp]) { if (_operationalSeed == null) { throw Exception('Operational seed not set. Server needs to be registered first.'); } timestamp ??= DateTime.now(); final timeWindow = timestamp.millisecondsSinceEpoch ~/ 1000 ~/ timeWindowSeconds; return _generateHmacSha256(_operationalSeed!, timeWindow.toString() + secretSalt); } /// Validate rolling code (checks current and previous window) static bool validateOperationalCode(String receivedCode, [DateTime? timestamp]) { if (_operationalSeed == null) { return false; } timestamp ??= DateTime.now(); final currentWindow = timestamp.millisecondsSinceEpoch ~/ 1000 ~/ timeWindowSeconds; // Check current window final currentExpected = _generateHmacSha256(_operationalSeed!, currentWindow.toString() + secretSalt); if (receivedCode == currentExpected) { return true; } // Check previous window (10-minute tolerance total) final previousExpected = _generateHmacSha256(_operationalSeed!, (currentWindow - 1).toString() + secretSalt); return receivedCode == previousExpected; } /// Set operational seed after successful registration static void setOperationalSeed(String seed) { _operationalSeed = seed; print('Operational seed set for server $_serverId'); } /// Get current operational seed static String? get operationalSeed => _operationalSeed; /// Get server ID static String? get serverId => _serverId; /// Check if server is registered (has operational seed) static bool get isRegistered => _operationalSeed != null; /// Generate HMAC-SHA256 hash static String _generateHmacSha256(String key, String message) { final keyBytes = utf8.encode(key); final messageBytes = utf8.encode(key + message); final hmac = Hmac(sha256, keyBytes); final digest = hmac.convert(messageBytes); return digest.toString(); } }