Merge pull request #84 from ente-io/handle_logout

Handle invalid sessions
This commit is contained in:
Neeraj Gupta 2023-04-04 16:14:38 +05:30 committed by GitHub
commit 33c3b997ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 120 additions and 27 deletions

View file

@ -0,0 +1,3 @@
import 'package:ente_auth/events/event.dart';
class TriggerLogoutEvent extends Event {}

View file

@ -103,24 +103,32 @@ class AuthenticatorGateway {
}
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
final response = await _dio.get(
_basedEndpoint + "/entity/diff",
queryParameters: {
"sinceTime": sinceTime,
"limit": limit,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
try {
final response = await _dio.get(
_basedEndpoint + "/entity/diff",
queryParameters: {
"sinceTime": sinceTime,
"limit": limit,
},
),
);
final List<AuthEntity> authEntities = <AuthEntity>[];
final diff = response.data["diff"] as List;
for (var entry in diff) {
final AuthEntity entity = AuthEntity.fromMap(entry);
authEntities.add(entity);
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final List<AuthEntity> authEntities = <AuthEntity>[];
final diff = response.data["diff"] as List;
for (var entry in diff) {
final AuthEntity entity = AuthEntity.fromMap(entry);
authEntities.add(entity);
}
return authEntities;
} catch (e) {
if (e is DioError && e.response?.statusCode == 401) {
throw UnauthorizedError();
} else {
rethrow;
}
}
return authEntities;
}
}

View file

@ -1,5 +1,4 @@
{
"@@locale": "de",
"counterAppBarTitle": "Zähler",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"

View file

@ -1,5 +1,4 @@
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
@ -14,6 +13,12 @@
"codeSecretKeyHint" : "Secret Key",
"codeAccountHint": "Account (you@domain.com)",
"accountKeyType": "Type of key",
"sessionExpired": "Session expired",
"@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired"
},
"pleaseLoginAgain": "Please login again",
"loggingOut" : "Logging out...",
"timeBasedKeyType": "Time based (TOTP)",
"counterBasedKeyType": "Counter based (HOTP)",
"saveAction": "Save",
@ -58,7 +63,7 @@
"supportDevs" : "Subscribe to <bold-green>ente</bold-green> to support this project.",
"supportDiscount" : "Use coupon code \"AUTH\" to get 10% off first year",
"changeEmail": "change email",
"ok": "OK",
"ok": "Ok",
"cancel": "Cancel",
"yes": "Yes",
"no": "No",

View file

@ -1,5 +1,4 @@
{
"@@locale": "es",
"counterAppBarTitle": "Contador",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"

View file

@ -1,5 +1,4 @@
{
"@@locale": "fr",
"counterAppBarTitle": "Compteur",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"

View file

@ -1,5 +1,4 @@
{
"@@locale": "it",
"counterAppBarTitle": "Contatore",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"

View file

@ -1,5 +1,4 @@
{
"@@locale": "nl",
"counterAppBarTitle": "Teller",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"

View file

@ -1,3 +1,2 @@
{
"@@locale": "pt"
}

View file

@ -1,3 +1,2 @@
{
"@@locale": "zh"
}

View file

@ -5,15 +5,18 @@ import 'package:flutter/material.dart';
@immutable
class LocalAuthEntity {
final int generatedID;
// id can be null if a code has been scanned locally but it's yet to be
// synced with the remote server.
final String? id;
final String encryptedData;
final String header;
// createdAt and updateAt will be equal to local time of creation or updation
// till remote sync is completed.
final int createdAt;
final int updatedAt;
// shouldSync indicates that the entry was locally created or updated. The
// app should try to sync it to the server during next sync
final bool shouldSync;

View file

@ -1,10 +1,15 @@
// ignore_for_file: import_of_legacy_library_into_null_safe
import 'dart:async';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/ui/account/email_entry_page.dart';
import 'package:ente_auth/ui/account/login_page.dart';
import 'package:ente_auth/ui/account/logout_dialog.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/account/password_reentry_page.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
@ -19,6 +24,23 @@ class OnboardingPage extends StatefulWidget {
}
class _OnboardingPageState extends State<OnboardingPage> {
late StreamSubscription<TriggerLogoutEvent> _triggerLogoutEvent;
@override
void initState() {
_triggerLogoutEvent =
Bus.instance.on<TriggerLogoutEvent>().listen((event) async {
await autoLogoutAlert(context);
});
super.initState();
}
@override
void dispose() {
_triggerLogoutEvent.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("Building OnboardingPage");

View file

@ -8,6 +8,7 @@ import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/events/codes_updated_event.dart';
import 'package:ente_auth/events/signed_in_event.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import 'package:ente_auth/gateway/authenticator.dart';
import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/auth_key.dart';
@ -132,6 +133,13 @@ class AuthenticatorService {
await _localToRemoteSync();
_logger.info("local push completed");
Bus.instance.fire(CodesUpdatedEvent());
} on UnauthorizedError {
if ((await _db.removeSyncedData()) > 0) {
Bus.instance.fire(CodesUpdatedEvent());
}
debugPrint("Firing logout event");
Bus.instance.fire(TriggerLogoutEvent());
} catch (e) {
_logger.severe("Failed to sync with remote", e);
}
@ -140,7 +148,7 @@ class AuthenticatorService {
Future<void> _remoteToLocalSync() async {
_logger.info('Initiating remote to local sync');
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
_logger.info("Current synctime is " + lastSyncTime.toString());
_logger.info("Current sync is " + lastSyncTime.toString());
const int fetchLimit = 500;
final List<AuthEntity> result =
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);

View file

@ -135,6 +135,13 @@ class AuthenticatorDB {
return _convertRows(rows);
}
// removeSyncedData will remove all the data which is synced with the server
Future<int> removeSyncedData() async {
final db = await instance.database;
return await db
.delete(entityTable, where: 'shouldSync = ?', whereArgs: [0]);
}
// deleteByID will prefer generated id if both ids are passed during deletion
Future<void> deleteByIDs({List<int>? generatedIDs, List<String>? ids}) async {
final db = await instance.database;

View file

@ -0,0 +1,36 @@
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:flutter/material.dart';
Future<void> autoLogoutAlert(BuildContext context) async {
final l10n = context.l10n;
final AlertDialog alert = AlertDialog(
title: Text(l10n.sessionExpired),
content: Text(l10n.pleaseLoginAgain),
actions: [
TextButton(
child: Text(
l10n.ok,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop('dialog');
Navigator.of(context).popUntil((route) => route.isFirst);
final dialog = createProgressDialog(context, l10n.loggingOut);
await dialog.show();
await Configuration.instance.logout();
await dialog.hide();
},
),
],
);
await showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}

View file

@ -7,12 +7,14 @@ import 'dart:ui';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/events/codes_updated_event.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/account/logout_dialog.dart';
import 'package:ente_auth/ui/code_widget.dart';
import 'package:ente_auth/ui/common/loading_widget.dart';
import 'package:ente_auth/ui/scanner_page.dart';
@ -43,6 +45,7 @@ class _HomePageState extends State<HomePage> {
List<Code> _codes = [];
List<Code> _filteredCodes = [];
StreamSubscription<CodesUpdatedEvent>? _streamSubscription;
StreamSubscription<TriggerLogoutEvent>? _triggerLogoutEvent;
@override
void initState() {
@ -51,6 +54,10 @@ class _HomePageState extends State<HomePage> {
_streamSubscription = Bus.instance.on<CodesUpdatedEvent>().listen((event) {
_loadCodes();
});
_triggerLogoutEvent =
Bus.instance.on<TriggerLogoutEvent>().listen((event) async {
await autoLogoutAlert(context);
});
super.initState();
}
@ -84,6 +91,7 @@ class _HomePageState extends State<HomePage> {
@override
void dispose() {
_streamSubscription?.cancel();
_triggerLogoutEvent?.cancel();
_textController.removeListener(_applyFiltering);
super.dispose();
}