Merge pull request #84 from ente-io/handle_logout
Handle invalid sessions
This commit is contained in:
commit
33c3b997ed
16 changed files with 120 additions and 27 deletions
3
lib/events/trigger_logout_event.dart
Normal file
3
lib/events/trigger_logout_event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class TriggerLogoutEvent extends Event {}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"counterAppBarTitle": "Zähler",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"counterAppBarTitle": "Contador",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"counterAppBarTitle": "Compteur",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"counterAppBarTitle": "Contatore",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"@@locale": "nl",
|
||||
"counterAppBarTitle": "Teller",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
{
|
||||
"@@locale": "pt"
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
{
|
||||
"@@locale": "zh"
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
36
lib/ui/account/logout_dialog.dart
Normal file
36
lib/ui/account/logout_dialog.dart
Normal 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;
|
||||
},
|
||||
);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue