fix: harden manual cloud upload authentication
This commit is contained in:
parent
825f327549
commit
d8abc78a40
|
|
@ -37,14 +37,24 @@ class FirebaseSyncProvider implements SyncProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> requiresAuthentication() async => _authService.currentUser == null;
|
Future<bool> requiresAuthentication() async {
|
||||||
|
final user = _authService.currentUser;
|
||||||
|
return user == null || user.isAnonymous;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SyncResult> uploadRide(SyncItem item) async {
|
Future<SyncResult> uploadRide(SyncItem item) async {
|
||||||
if (!await isAvailable()) return SyncResult.temporaryFailure('Firebase services are unavailable or unconfigured. Ride remains safely stored locally.');
|
if (!await isAvailable()) return SyncResult.temporaryFailure('Firebase services are unavailable or unconfigured. Ride remains safely stored locally.');
|
||||||
final user = _authService.currentUser;
|
final user = _authService.currentUser;
|
||||||
if (user == null || user.isAnonymous) return SyncResult.authRequired(message: 'Sign in to MotorCloud is required for cloud backup.');
|
if (user == null || user.isAnonymous) return SyncResult.authRequired(message: 'Sign in to MotorCloud is required for cloud backup.');
|
||||||
final idToken = await _authService.getIdToken();
|
|
||||||
|
// Refresh the Firebase ID token before a manual upload. This prevents a
|
||||||
|
// recently completed sign-in from being rejected because the cached token
|
||||||
|
// has not yet propagated through the app's auth abstraction.
|
||||||
|
var idToken = await _authService.getIdToken();
|
||||||
|
if (idToken == null || idToken.isEmpty) {
|
||||||
|
idToken = await _authService.getIdToken(true);
|
||||||
|
}
|
||||||
if (idToken == null || idToken.isEmpty) return SyncResult.authRequired(message: 'Valid Firebase authentication token required for cloud sync.');
|
if (idToken == null || idToken.isEmpty) return SyncResult.authRequired(message: 'Valid Firebase authentication token required for cloud sync.');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -59,13 +69,31 @@ class FirebaseSyncProvider implements SyncProvider {
|
||||||
if (zipBytes == null || zipBytes.isEmpty) return SyncResult.permanentFailure('Failed generating compressed telemetry bundle for ride #${item.localRideId}.');
|
if (zipBytes == null || zipBytes.isEmpty) return SyncResult.permanentFailure('Failed generating compressed telemetry bundle for ride #${item.localRideId}.');
|
||||||
final sha256Hash = sha256.convert(zipBytes).toString();
|
final sha256Hash = sha256.convert(zipBytes).toString();
|
||||||
|
|
||||||
final r2Result = await _r2UploadService.uploadTelemetryBundle(
|
var r2Result = await _r2UploadService.uploadTelemetryBundle(
|
||||||
idToken: idToken,
|
idToken: idToken,
|
||||||
remoteRideId: remoteRideId,
|
remoteRideId: remoteRideId,
|
||||||
zipBytes: zipBytes,
|
zipBytes: zipBytes,
|
||||||
sha256Hash: sha256Hash,
|
sha256Hash: sha256Hash,
|
||||||
appVersion: appIdentity.semanticVersion,
|
appVersion: appIdentity.semanticVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If the gateway rejects the first token as unauthorized, refresh once
|
||||||
|
// and retry. This specifically handles token rotation/expiry without
|
||||||
|
// turning a valid signed-in session into a false authentication error.
|
||||||
|
if (!r2Result.success && r2Result.requiresAuth) {
|
||||||
|
final refreshedToken = await _authService.getIdToken(true);
|
||||||
|
if (refreshedToken != null && refreshedToken.isNotEmpty && refreshedToken != idToken) {
|
||||||
|
idToken = refreshedToken;
|
||||||
|
r2Result = await _r2UploadService.uploadTelemetryBundle(
|
||||||
|
idToken: idToken,
|
||||||
|
remoteRideId: remoteRideId,
|
||||||
|
zipBytes: zipBytes,
|
||||||
|
sha256Hash: sha256Hash,
|
||||||
|
appVersion: appIdentity.semanticVersion,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!r2Result.success) {
|
if (!r2Result.success) {
|
||||||
if (r2Result.requiresAuth) return SyncResult.authRequired(message: r2Result.errorMessage ?? 'R2 Gateway authentication required');
|
if (r2Result.requiresAuth) return SyncResult.authRequired(message: r2Result.errorMessage ?? 'R2 Gateway authentication required');
|
||||||
if (r2Result.isRetryable) return SyncResult.temporaryFailure(r2Result.errorMessage ?? 'Temporary R2 upload error');
|
if (r2Result.isRetryable) return SyncResult.temporaryFailure(r2Result.errorMessage ?? 'Temporary R2 upload error');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue