Fix: handle incorrect device time during code generation

This commit is contained in:
Neeraj Gupta
2025-05-30 16:24:43 +05:30
parent f045dc8e04
commit b560e5b71a
4 changed files with 44 additions and 9 deletions

View File

@@ -73,7 +73,10 @@ class AuthenticatorGateway {
);
}
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
Future<(List<AuthEntity>, int?)> getDiff(
int sinceTime, {
int limit = 500,
}) async {
try {
final response = await _enteDio.get(
"/authenticator/entity/diff",
@@ -84,11 +87,12 @@ class AuthenticatorGateway {
);
final List<AuthEntity> authEntities = <AuthEntity>[];
final diff = response.data["diff"] as List;
final int? unixTimeInMicroSeconds = response.data["timestamp"] as int?;
for (var entry in diff) {
final AuthEntity entity = AuthEntity.fromMap(entry);
authEntities.add(entity);
}
return authEntities;
return (authEntities, unixTimeInMicroSeconds);
} catch (e) {
if (e is DioException && e.response?.statusCode == 401) {
throw UnauthorizedError();

View File

@@ -13,6 +13,7 @@ import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/auth_key.dart';
import 'package:ente_auth/models/authenticator/entity_result.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/store/authenticator_db.dart';
import 'package:ente_auth/store/offline_authenticator_db.dart';
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
@@ -194,8 +195,13 @@ class AuthenticatorService {
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
_logger.info("Current sync is $lastSyncTime");
const int fetchLimit = 500;
final List<AuthEntity> result =
late final List<AuthEntity> result;
late final int? epochTimeInMicroseconds;
(result, epochTimeInMicroseconds) =
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
PreferenceService.instance
.computeAndStoreTimeOffset(epochTimeInMicroseconds);
_logger.info("${result.length} entries fetched from remote");
if (result.isEmpty) {
return;

View File

@@ -18,6 +18,7 @@ class PreferenceService {
late final SharedPreferences _prefs;
static const kHasShownCoachMarkKey = "has_shown_coach_mark_v2";
static const kLocalTimeOffsetKey = "local_time_offset";
static const kShouldShowLargeIconsKey = "should_show_large_icons";
static const kShouldHideCodesKey = "should_hide_codes";
static const kShouldAutoFocusOnSearchBar = "should_auto_focus_on_search_bar";
@@ -114,4 +115,24 @@ class PreferenceService {
return installedTimeinMillis;
}
}
// localEpochOffsetInMilliSecond returns the local epoch offset in milliseconds.
// This is used to adjust the time for TOTP calculations when device local time is not in sync with actual time.
int timeOffsetInMilliSeconds() {
return _prefs.getInt(kLocalTimeOffsetKey) ?? 0;
}
void computeAndStoreTimeOffset(
int? epochTimeInMicroseconds,
) {
if (epochTimeInMicroseconds == null) {
_prefs.remove(kLocalTimeOffsetKey);
return;
}
int serverEpochTimeInMilliSecond = epochTimeInMicroseconds ~/ 1000;
int localEpochTimeInMilliSecond = DateTime.now().millisecondsSinceEpoch;
int localEpochOffset =
serverEpochTimeInMilliSecond - localEpochTimeInMilliSecond;
_prefs.setInt(kLocalTimeOffsetKey, localEpochOffset);
}
}

View File

@@ -1,8 +1,14 @@
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:flutter/foundation.dart';
import 'package:otp/otp.dart' as otp;
import 'package:steam_totp/steam_totp.dart';
int millisecondsSinceEpoch() {
return DateTime.now().millisecondsSinceEpoch +
PreferenceService.instance.timeOffsetInMilliSeconds();
}
String getOTP(Code code) {
if (code.type == Type.steam || code.issuer.toLowerCase() == 'steam') {
return _getSteamCode(code);
@@ -12,7 +18,7 @@ String getOTP(Code code) {
}
return otp.OTP.generateTOTPCodeString(
getSanitizedSecret(code.secret),
DateTime.now().millisecondsSinceEpoch,
millisecondsSinceEpoch(),
length: code.digits,
interval: code.period,
algorithm: _getAlgorithm(code),
@@ -34,7 +40,7 @@ String _getSteamCode(Code code, [bool isNext = false]) {
final SteamTOTP steamtotp = SteamTOTP(secret: code.secret);
return steamtotp.generate(
DateTime.now().millisecondsSinceEpoch ~/ 1000 + (isNext ? code.period : 0),
millisecondsSinceEpoch() ~/ 1000 + (isNext ? code.period : 0),
);
}
@@ -44,7 +50,7 @@ String getNextTotp(Code code) {
}
return otp.OTP.generateTOTPCodeString(
getSanitizedSecret(code.secret),
DateTime.now().millisecondsSinceEpoch + code.period * 1000,
millisecondsSinceEpoch() + code.period * 1000,
length: code.digits,
interval: code.period,
algorithm: _getAlgorithm(code),
@@ -56,9 +62,7 @@ String getNextTotp(Code code) {
// It returns the start time and a list of future codes.
(int, List<String>) generateFutureTotpCodes(Code code, int count) {
final int startTime =
((DateTime.now().millisecondsSinceEpoch ~/ 1000) ~/ code.period) *
code.period *
1000;
((millisecondsSinceEpoch() ~/ 1000) ~/ code.period) * code.period * 1000;
final String secret = getSanitizedSecret(code.secret);
final List<String> codes = [];
if (code.type == Type.steam || code.issuer.toLowerCase() == 'steam') {