Add session timeout

This commit is contained in:
Hillel Coren 2021-02-21 23:38:15 +02:00
parent 66591fd895
commit 1cb9bbb79d
5 changed files with 341 additions and 258 deletions

View File

@ -14,6 +14,7 @@ import 'package:invoiceninja_flutter/ui/app/app_builder.dart';
import 'package:invoiceninja_flutter/ui/app/change_layout_banner.dart'; import 'package:invoiceninja_flutter/ui/app/change_layout_banner.dart';
import 'package:invoiceninja_flutter/ui/app/main_screen.dart'; import 'package:invoiceninja_flutter/ui/app/main_screen.dart';
import 'package:invoiceninja_flutter/ui/app/screen_imports.dart'; import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
import 'package:invoiceninja_flutter/ui/app/session_timeout.dart';
import 'package:invoiceninja_flutter/ui/auth/init_screen.dart'; import 'package:invoiceninja_flutter/ui/auth/init_screen.dart';
import 'package:invoiceninja_flutter/ui/auth/lock_screen.dart'; import 'package:invoiceninja_flutter/ui/auth/lock_screen.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart'; import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
@ -168,6 +169,7 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreProvider<AppState>( return StoreProvider<AppState>(
store: widget.store, store: widget.store,
child: SessionTimeout(
child: AppBuilder(builder: (context) { child: AppBuilder(builder: (context) {
final store = widget.store; final store = widget.store;
final state = store.state; final state = store.state;
@ -186,8 +188,9 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
backgroundColor: backgroundColor:
state.prefState.enableDarkMode ? Colors.white : Colors.black, state.prefState.enableDarkMode ? Colors.white : Colors.black,
textStyle: TextStyle( textStyle: TextStyle(
color: color: state.prefState.enableDarkMode
state.prefState.enableDarkMode ? Colors.black87 : Colors.white, ? Colors.black87
: Colors.white,
), ),
child: MaterialApp( child: MaterialApp(
supportedLocales: kLanguages supportedLocales: kLanguages
@ -238,15 +241,19 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
canvasColor: Colors.white, canvasColor: Colors.white,
cardColor: Colors.white, cardColor: Colors.white,
bottomAppBarColor: Colors.white, bottomAppBarColor: Colors.white,
primaryColorDark: primaryColorDark: hasAccentColor
hasAccentColor ? accentColor : const Color(0xFF0D5D91), ? accentColor
primaryColorLight: : const Color(0xFF0D5D91),
hasAccentColor ? accentColor : const Color(0xFF5dabf4), primaryColorLight: hasAccentColor
buttonColor: ? accentColor
hasAccentColor ? accentColor : const Color(0xFF0D5D91), : const Color(0xFF5dabf4),
buttonColor: hasAccentColor
? accentColor
: const Color(0xFF0D5D91),
scaffoldBackgroundColor: const Color(0xFFE7EBEE), scaffoldBackgroundColor: const Color(0xFFE7EBEE),
tabBarTheme: TabBarTheme( tabBarTheme: TabBarTheme(
labelColor: hasAccentColor ? Colors.white : Colors.black, labelColor:
hasAccentColor ? Colors.white : Colors.black,
unselectedLabelColor: hasAccentColor unselectedLabelColor: hasAccentColor
? Colors.white.withOpacity(.65) ? Colors.white.withOpacity(.65)
: Colors.black.withOpacity(.65), : Colors.black.withOpacity(.65),
@ -269,12 +276,11 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
color: hasAccentColor ? Colors.white : accentColor, color: hasAccentColor ? Colors.white : accentColor,
), ),
textTheme: TextTheme( textTheme: TextTheme(
headline6: Theme.of(context) headline6:
.textTheme Theme.of(context).textTheme.headline6.copyWith(
.headline6 color: hasAccentColor
.copyWith( ? Colors.white
color: : Colors.black,
hasAccentColor ? Colors.white : Colors.black,
), ),
), ),
), ),
@ -300,11 +306,15 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
InvoiceScreen.route: (context) => InvoiceScreenBuilder(), InvoiceScreen.route: (context) => InvoiceScreenBuilder(),
InvoiceViewScreen.route: (context) => InvoiceViewScreen(), InvoiceViewScreen.route: (context) => InvoiceViewScreen(),
InvoiceEditScreen.route: (context) => InvoiceEditScreen(), InvoiceEditScreen.route: (context) => InvoiceEditScreen(),
InvoiceEmailScreen.route: (context) => InvoiceEmailScreen(), InvoiceEmailScreen.route: (context) =>
InvoiceEmailScreen(),
InvoicePdfScreen.route: (context) => InvoicePdfScreen(), InvoicePdfScreen.route: (context) => InvoicePdfScreen(),
DocumentScreen.route: (context) => DocumentScreenBuilder(), DocumentScreen.route: (context) =>
DocumentViewScreen.route: (context) => DocumentViewScreen(), DocumentScreenBuilder(),
DocumentEditScreen.route: (context) => DocumentEditScreen(), DocumentViewScreen.route: (context) =>
DocumentViewScreen(),
DocumentEditScreen.route: (context) =>
DocumentEditScreen(),
ExpenseScreen.route: (context) => ExpenseScreenBuilder(), ExpenseScreen.route: (context) => ExpenseScreenBuilder(),
ExpenseViewScreen.route: (context) => ExpenseViewScreen(), ExpenseViewScreen.route: (context) => ExpenseViewScreen(),
ExpenseEditScreen.route: (context) => ExpenseEditScreen(), ExpenseEditScreen.route: (context) => ExpenseEditScreen(),
@ -375,12 +385,14 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
GroupScreenBuilder(), GroupScreenBuilder(),
GroupViewScreen.route: (context) => GroupViewScreen(), GroupViewScreen.route: (context) => GroupViewScreen(),
GroupEditScreen.route: (context) => GroupEditScreen(), GroupEditScreen.route: (context) => GroupEditScreen(),
SettingsScreen.route: (context) => SettingsScreenBuilder(), SettingsScreen.route: (context) =>
SettingsScreenBuilder(),
ReportsScreen.route: (context) => ReportsScreenBuilder(), ReportsScreen.route: (context) => ReportsScreenBuilder(),
CompanyDetailsScreen.route: (context) => CompanyDetailsScreen.route: (context) =>
CompanyDetailsScreen(), CompanyDetailsScreen(),
UserDetailsScreen.route: (context) => UserDetailsScreen(), UserDetailsScreen.route: (context) => UserDetailsScreen(),
LocalizationScreen.route: (context) => LocalizationScreen(), LocalizationScreen.route: (context) =>
LocalizationScreen(),
OnlinePaymentsScreen.route: (context) => OnlinePaymentsScreen.route: (context) =>
OnlinePaymentsScreen(), OnlinePaymentsScreen(),
CompanyGatewayScreen.route: (context) => CompanyGatewayScreen.route: (context) =>
@ -398,22 +410,26 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
ProductSettingsScreen(), ProductSettingsScreen(),
ExpenseSettingsScreen.route: (context) => ExpenseSettingsScreen.route: (context) =>
ExpenseSettingsScreen(), ExpenseSettingsScreen(),
TaskSettingsScreen.route: (context) => TaskSettingsScreen(), TaskSettingsScreen.route: (context) =>
TaskSettingsScreen(),
IntegrationSettingsScreen.route: (context) => IntegrationSettingsScreen.route: (context) =>
IntegrationSettingsScreen(), IntegrationSettingsScreen(),
ImportExportScreen.route: (context) => ImportExportScreen(), ImportExportScreen.route: (context) =>
ImportExportScreen(),
DeviceSettingsScreen.route: (context) => DeviceSettingsScreen.route: (context) =>
DeviceSettingsScreen(), DeviceSettingsScreen(),
AccountManagementScreen.route: (context) => AccountManagementScreen.route: (context) =>
AccountManagementScreen(), AccountManagementScreen(),
CustomFieldsScreen.route: (context) => CustomFieldsScreen(), CustomFieldsScreen.route: (context) =>
CustomFieldsScreen(),
GeneratedNumbersScreen.route: (context) => GeneratedNumbersScreen.route: (context) =>
GeneratedNumbersScreen(), GeneratedNumbersScreen(),
WorkflowSettingsScreen.route: (context) => WorkflowSettingsScreen.route: (context) =>
WorkflowSettingsScreen(), WorkflowSettingsScreen(),
InvoiceDesignScreen.route: (context) => InvoiceDesignScreen.route: (context) =>
InvoiceDesignScreen(), InvoiceDesignScreen(),
ClientPortalScreen.route: (context) => ClientPortalScreen(), ClientPortalScreen.route: (context) =>
ClientPortalScreen(),
BuyNowButtonsScreen.route: (context) => BuyNowButtonsScreen.route: (context) =>
BuyNowButtonsScreen(), BuyNowButtonsScreen(),
EmailSettingsScreen.route: (context) => EmailSettingsScreen.route: (context) =>
@ -429,6 +445,7 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
), ),
); );
}), }),
),
); );
} }
} }

View File

@ -95,9 +95,10 @@ class RecoverPasswordFailure implements StopLoading {
} }
class UserLogout implements PersistData, PersistUI { class UserLogout implements PersistData, PersistUI {
UserLogout(this.context); UserLogout(this.context, {this.navigate = true});
final BuildContext context; final BuildContext context;
final bool navigate;
} }
class UserSignUpRequest implements StartLoading { class UserSignUpRequest implements StartLoading {

View File

@ -56,8 +56,10 @@ Middleware<AppState> _createUserLogout() {
next(action); next(action);
if (action.navigate) {
Navigator.of(action.context).pushNamedAndRemoveUntil( Navigator.of(action.context).pushNamedAndRemoveUntil(
LoginScreen.route, (Route<dynamic> route) => false); LoginScreen.route, (Route<dynamic> route) => false);
}
store.dispatch(UpdateCurrentRoute(LoginScreen.route)); store.dispatch(UpdateCurrentRoute(LoginScreen.route));
}; };

View File

@ -0,0 +1,59 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
class SessionTimeout extends StatefulWidget {
const SessionTimeout({this.child});
final Widget child;
@override
_SessionTimeoutState createState() => _SessionTimeoutState();
}
class _SessionTimeoutState extends State<SessionTimeout> {
Timer _timer;
@override
void initState() {
super.initState();
if (!kIsWeb) {
return;
}
_timer = Timer.periodic(
Duration(seconds: 1),
(Timer timer) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final sessionTimeout = state.company.sessionTimeout;
final sessionLength = DateTime.now().millisecondsSinceEpoch -
state.userCompanyState.lastUpdated;
print('## Timeout: $sessionTimeout, Length: $sessionLength');
if (sessionTimeout != 0 && sessionLength > sessionTimeout) {
store.dispatch(UserLogout(context, navigate: false));
WebUtils.reloadBrowser();
}
},
);
}
@override
void dispose() {
_timer?.cancel();
_timer = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}

View File

@ -136,6 +136,10 @@ class _AccountManagementState extends State<AccountManagement>
child: Text(localization.never), child: Text(localization.never),
value: 0, value: 0,
), ),
DropdownMenuItem<int>(
child: Text('1 minute'),
value: 1000 * 60,
),
DropdownMenuItem<int>( DropdownMenuItem<int>(
child: Text(localization.countHours child: Text(localization.countHours
.replaceFirst(':count', '8')), .replaceFirst(':count', '8')),