Files
waylume_server/lib/services/rolling_codes_service.dart

92 lines
3.2 KiB
Dart

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<void> 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();
}
}