mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 08:07:59 -06:00
feat: add permission/user/group models, start integration into code
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
part 'local_user_account.g.dart';
|
||||
|
||||
@@ -9,23 +10,19 @@ class LocalUserAccount extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String serverUrl;
|
||||
|
||||
@HiveField(1)
|
||||
final String username;
|
||||
|
||||
@HiveField(2)
|
||||
final String? fullName;
|
||||
|
||||
@HiveField(3)
|
||||
final String id;
|
||||
|
||||
@HiveField(4)
|
||||
LocalUserSettings settings;
|
||||
|
||||
@HiveField(5)
|
||||
UserModel paperlessUser;
|
||||
|
||||
LocalUserAccount({
|
||||
required this.id,
|
||||
required this.serverUrl,
|
||||
required this.username,
|
||||
required this.settings,
|
||||
this.fullName,
|
||||
required this.paperlessUser,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
@@ -29,6 +28,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final LabelRepository _labelRepository;
|
||||
final SavedViewRepository _savedViewRepository;
|
||||
final PaperlessServerStatsApi _serverStatsApi;
|
||||
final PaperlessUserApi _userApi;
|
||||
final PaperlessUserApiV3? _userApiV3;
|
||||
|
||||
AuthenticationCubit(
|
||||
this._localAuthService,
|
||||
@@ -37,7 +38,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
this._labelRepository,
|
||||
this._savedViewRepository,
|
||||
this._serverStatsApi,
|
||||
) : super(const AuthenticationState());
|
||||
this._userApi, {
|
||||
PaperlessUserApiV3? userApiV3,
|
||||
}) : _userApiV3 = userApiV3,
|
||||
super(const AuthenticationState());
|
||||
|
||||
Future<void> login({
|
||||
required LoginFormCredentials credentials,
|
||||
@@ -45,89 +49,49 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
ClientCertificate? clientCertificate,
|
||||
}) async {
|
||||
assert(credentials.username != null && credentials.password != null);
|
||||
final localUserId = "${credentials.username}@$serverUrl";
|
||||
|
||||
_sessionManager.updateSettings(
|
||||
baseUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
);
|
||||
final token = await _authApi.login(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
);
|
||||
_sessionManager.updateSettings(
|
||||
baseUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
authToken: token,
|
||||
final serverUser = await _addUser(
|
||||
localUserId,
|
||||
serverUrl,
|
||||
credentials,
|
||||
clientCertificate,
|
||||
_sessionManager,
|
||||
);
|
||||
|
||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||
|
||||
final userId = "${credentials.username}@$serverUrl";
|
||||
|
||||
if (userAccountBox.containsKey(userId)) {
|
||||
throw Exception("User with id $userId already exists!");
|
||||
}
|
||||
|
||||
final fullName = await _fetchFullName();
|
||||
// Create user account
|
||||
await userAccountBox.put(
|
||||
userId,
|
||||
LocalUserAccount(
|
||||
id: userId,
|
||||
settings: LocalUserSettings(),
|
||||
serverUrl: serverUrl,
|
||||
username: credentials.username!,
|
||||
fullName: fullName,
|
||||
),
|
||||
);
|
||||
|
||||
// Create user state
|
||||
await userStateBox.put(
|
||||
userId,
|
||||
LocalUserAppState(userId: userId),
|
||||
);
|
||||
|
||||
// Save credentials in encrypted box
|
||||
final userCredentialsBox = await _getUserCredentialsBox();
|
||||
await userCredentialsBox.put(
|
||||
userId,
|
||||
UserCredentials(
|
||||
token: token,
|
||||
clientCertificate: clientCertificate,
|
||||
),
|
||||
);
|
||||
userCredentialsBox.close();
|
||||
final response = await _sessionManager.client.get("/api/");
|
||||
final apiVersion = response.headers["x-api-version"] as int;
|
||||
|
||||
// Mark logged in user as currently active user.
|
||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
globalSettings.currentLoggedInUser = userId;
|
||||
globalSettings.currentLoggedInUser = localUserId;
|
||||
await globalSettings.save();
|
||||
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
username: credentials.username,
|
||||
userId: userId,
|
||||
fullName: fullName,
|
||||
localUserId: localUserId,
|
||||
fullName: serverUser.fullName,
|
||||
apiVersion: apiVersion,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Switches to another account if it exists.
|
||||
Future<void> switchAccount(String userId) async {
|
||||
Future<void> switchAccount(String localUserId) async {
|
||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
if (globalSettings.currentLoggedInUser == userId) {
|
||||
if (globalSettings.currentLoggedInUser == localUserId) {
|
||||
return;
|
||||
}
|
||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
|
||||
if (!userAccountBox.containsKey(userId)) {
|
||||
debugPrint("User $userId not yet registered.");
|
||||
if (!userAccountBox.containsKey(localUserId)) {
|
||||
debugPrint("User $localUserId not yet registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
final account = userAccountBox.get(userId)!;
|
||||
final account = userAccountBox.get(localUserId)!;
|
||||
|
||||
if (account.settings.isBiometricAuthenticationEnabled) {
|
||||
final authenticated =
|
||||
@@ -139,12 +103,12 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
}
|
||||
|
||||
final credentialsBox = await _getUserCredentialsBox();
|
||||
if (!credentialsBox.containsKey(userId)) {
|
||||
if (!credentialsBox.containsKey(localUserId)) {
|
||||
await credentialsBox.close();
|
||||
debugPrint("Invalid authentication for $userId");
|
||||
debugPrint("Invalid authentication for $localUserId");
|
||||
return;
|
||||
}
|
||||
final credentials = credentialsBox.get(userId);
|
||||
final credentials = credentialsBox.get(localUserId);
|
||||
await credentialsBox.close();
|
||||
|
||||
await _resetExternalState();
|
||||
@@ -157,15 +121,19 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
);
|
||||
|
||||
await _reloadRepositories();
|
||||
globalSettings.currentLoggedInUser = userId;
|
||||
globalSettings.currentLoggedInUser = localUserId;
|
||||
await globalSettings.save();
|
||||
|
||||
final response = await _sessionManager.client.get("/api/");
|
||||
final apiVersion = response.headers["x-api-version"] as int;
|
||||
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
username: account.username,
|
||||
fullName: account.fullName,
|
||||
userId: userId,
|
||||
username: account.paperlessUser.username,
|
||||
fullName: account.paperlessUser.fullName,
|
||||
localUserId: localUserId,
|
||||
apiVersion: apiVersion,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -177,62 +145,16 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
required bool enableBiometricAuthentication,
|
||||
}) async {
|
||||
assert(credentials.password != null && credentials.username != null);
|
||||
final userId = "${credentials.username}@$serverUrl";
|
||||
|
||||
final userAccountsBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||
|
||||
if (userAccountsBox.containsKey(userId)) {
|
||||
throw Exception("User already exists");
|
||||
}
|
||||
// Creates a parallel session to get token and disposes of resources after.
|
||||
final sessionManager = SessionManager([
|
||||
DioHttpErrorInterceptor(),
|
||||
]);
|
||||
sessionManager.updateSettings(
|
||||
clientCertificate: clientCertificate,
|
||||
baseUrl: serverUrl,
|
||||
);
|
||||
final authApi = PaperlessAuthenticationApiImpl(sessionManager.client);
|
||||
|
||||
final token = await authApi.login(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
);
|
||||
sessionManager.resetSettings();
|
||||
|
||||
final fullName = await _fetchFullName();
|
||||
|
||||
await userAccountsBox.put(
|
||||
userId,
|
||||
LocalUserAccount(
|
||||
id: userId,
|
||||
serverUrl: serverUrl,
|
||||
username: credentials.username!,
|
||||
settings: LocalUserSettings(
|
||||
isBiometricAuthenticationEnabled: enableBiometricAuthentication,
|
||||
),
|
||||
fullName: fullName,
|
||||
),
|
||||
final localUserId = "${credentials.username}@$serverUrl";
|
||||
await _addUser(
|
||||
localUserId,
|
||||
serverUrl,
|
||||
credentials,
|
||||
clientCertificate,
|
||||
_sessionManager,
|
||||
);
|
||||
|
||||
await userStateBox.put(
|
||||
userId,
|
||||
LocalUserAppState(
|
||||
userId: userId,
|
||||
),
|
||||
);
|
||||
|
||||
final userCredentialsBox = await _getUserCredentialsBox();
|
||||
await userCredentialsBox.put(
|
||||
userId,
|
||||
UserCredentials(
|
||||
token: token,
|
||||
clientCertificate: clientCertificate,
|
||||
),
|
||||
);
|
||||
await userCredentialsBox.close();
|
||||
return userId;
|
||||
return localUserId;
|
||||
}
|
||||
|
||||
Future<void> removeAccount(String userId) async {
|
||||
@@ -287,11 +209,16 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
baseUrl: userAccount.serverUrl,
|
||||
serverInformation: PaperlessServerInformationModel(),
|
||||
);
|
||||
final response = await _sessionManager.client.get("/api/");
|
||||
final apiVersion = response.headers["x-api-version"] as int;
|
||||
|
||||
emit(
|
||||
AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
showBiometricAuthenticationScreen: false,
|
||||
username: userAccount.username,
|
||||
username: userAccount.paperlessUser.username,
|
||||
apiVersion: apiVersion,
|
||||
fullName: userAccount.paperlessUser.fullName,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -343,12 +270,68 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
]);
|
||||
}
|
||||
|
||||
Future<String?> _fetchFullName() async {
|
||||
try {
|
||||
final uiSettings = await _serverStatsApi.getUiSettings();
|
||||
return uiSettings.displayName;
|
||||
} catch (error) {
|
||||
return null;
|
||||
Future<UserModel> _addUser(
|
||||
String localUserId,
|
||||
String serverUrl,
|
||||
LoginFormCredentials credentials,
|
||||
ClientCertificate? clientCert,
|
||||
SessionManager sessionManager,
|
||||
) async {
|
||||
assert(credentials.username != null && credentials.password != null);
|
||||
|
||||
sessionManager.updateSettings(
|
||||
baseUrl: serverUrl,
|
||||
clientCertificate: clientCert,
|
||||
);
|
||||
final authApi = PaperlessAuthenticationApiImpl(sessionManager.client);
|
||||
|
||||
final token = await authApi.login(
|
||||
username: credentials.username!,
|
||||
password: credentials.password!,
|
||||
);
|
||||
sessionManager.updateSettings(
|
||||
baseUrl: serverUrl,
|
||||
clientCertificate: clientCert,
|
||||
authToken: token,
|
||||
);
|
||||
|
||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||
|
||||
if (userAccountBox.containsKey(localUserId)) {
|
||||
throw Exception("User with id $localUserId already exists!");
|
||||
}
|
||||
|
||||
final serverUserId = await _userApi.findCurrentUserId();
|
||||
final serverUser = await _userApi.find(serverUserId);
|
||||
|
||||
// Create user account
|
||||
await userAccountBox.put(
|
||||
localUserId,
|
||||
LocalUserAccount(
|
||||
id: localUserId,
|
||||
settings: LocalUserSettings(),
|
||||
serverUrl: serverUrl,
|
||||
paperlessUser: serverUser,
|
||||
),
|
||||
);
|
||||
|
||||
// Create user state
|
||||
await userStateBox.put(
|
||||
localUserId,
|
||||
LocalUserAppState(userId: localUserId),
|
||||
);
|
||||
|
||||
// Save credentials in encrypted box
|
||||
final userCredentialsBox = await _getUserCredentialsBox();
|
||||
await userCredentialsBox.put(
|
||||
localUserId,
|
||||
UserCredentials(
|
||||
token: token,
|
||||
clientCertificate: clientCert,
|
||||
),
|
||||
);
|
||||
userCredentialsBox.close();
|
||||
return serverUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@ class AuthenticationState with EquatableMixin {
|
||||
final bool isAuthenticated;
|
||||
final String? username;
|
||||
final String? fullName;
|
||||
final String? userId;
|
||||
final String? localUserId;
|
||||
final int? apiVersion;
|
||||
|
||||
const AuthenticationState({
|
||||
this.isAuthenticated = false,
|
||||
this.showBiometricAuthenticationScreen = false,
|
||||
this.username,
|
||||
this.fullName,
|
||||
this.userId,
|
||||
this.localUserId,
|
||||
this.apiVersion,
|
||||
});
|
||||
|
||||
AuthenticationState copyWith({
|
||||
@@ -20,7 +22,8 @@ class AuthenticationState with EquatableMixin {
|
||||
bool? showBiometricAuthenticationScreen,
|
||||
String? username,
|
||||
String? fullName,
|
||||
String? userId,
|
||||
String? localUserId,
|
||||
int? apiVersion,
|
||||
}) {
|
||||
return AuthenticationState(
|
||||
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
||||
@@ -28,16 +31,18 @@ class AuthenticationState with EquatableMixin {
|
||||
showBiometricAuthenticationScreen ?? this.showBiometricAuthenticationScreen,
|
||||
username: username ?? this.username,
|
||||
fullName: fullName ?? this.fullName,
|
||||
userId: userId ?? this.userId,
|
||||
localUserId: localUserId ?? this.localUserId,
|
||||
apiVersion: apiVersion ?? this.apiVersion,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
userId,
|
||||
localUserId,
|
||||
username,
|
||||
fullName,
|
||||
isAuthenticated,
|
||||
showBiometricAuthenticationScreen,
|
||||
apiVersion,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
@@ -33,18 +29,18 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
.whereNot((element) => element == globalSettings.currentLoggedInUser)
|
||||
.toList();
|
||||
return SimpleDialog(
|
||||
insetPadding: EdgeInsets.all(24),
|
||||
contentPadding: EdgeInsets.all(8),
|
||||
insetPadding: const EdgeInsets.all(24),
|
||||
contentPadding: const EdgeInsets.all(8),
|
||||
title: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: CloseButton(),
|
||||
),
|
||||
Center(child: Text(S.of(context)!.accounts)),
|
||||
],
|
||||
), //TODO: INTL
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
@@ -103,7 +99,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
isThreeLine: account.fullName != null,
|
||||
leading: UserAvatar(
|
||||
account: account,
|
||||
userId: userId,
|
||||
@@ -116,7 +112,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S.of(context)!.switchAccount),
|
||||
leading: Icon(Icons.switch_account_rounded),
|
||||
leading: const Icon(Icons.switch_account_rounded),
|
||||
),
|
||||
value: 0,
|
||||
),
|
||||
@@ -124,7 +120,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S.of(context)!.remove),
|
||||
leading: Icon(
|
||||
leading: const Icon(
|
||||
Icons.person_remove,
|
||||
color: Colors.red,
|
||||
),
|
||||
@@ -135,7 +131,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S.of(context)!.logout),
|
||||
leading: Icon(
|
||||
leading: const Icon(
|
||||
Icons.person_remove,
|
||||
color: Colors.red,
|
||||
),
|
||||
|
||||
@@ -297,7 +297,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
builder: (context, authentication) {
|
||||
if (authentication.isAuthenticated) {
|
||||
return HomeRoute(
|
||||
key: ValueKey(authentication.userId),
|
||||
key: ValueKey(authentication.localUserId),
|
||||
);
|
||||
} else if (authentication.showBiometricAuthenticationScreen) {
|
||||
return const VerifyIdentityPage();
|
||||
|
||||
Reference in New Issue
Block a user