Added account permissions and ui for recipient

This commit is contained in:
Arseni
2025-11-26 13:03:52 +03:00
parent fcb5ab4f2c
commit 357af99564
23 changed files with 507 additions and 70 deletions

View File

@@ -0,0 +1,97 @@
import 'package:flutter/foundation.dart';
import 'package:pshared/api/requests/login_data.dart';
import 'package:pshared/models/account/account.dart';
import 'package:pshared/models/auth/login_outcome.dart';
import 'package:pshared/models/auth/pending_login.dart';
import 'package:pshared/models/describable.dart';
import 'package:pweb/providers/permissions.dart';
import 'package:pweb/services/accounts.dart';
class AccountProvider extends ChangeNotifier {
final AccountsService _accountsService;
final PermissionsProvider _permissionsProvider;
AccountProvider({
required AccountsService accountsService,
required PermissionsProvider permissionsProvider,
}) : _accountsService = accountsService,
_permissionsProvider = permissionsProvider;
Account? _account;
bool _isLoading = false;
Object? _error;
Account? get account => _account;
bool get isLoading => _isLoading;
Object? get error => _error;
bool get isLoggedIn => _account != null;
PendingLogin? get pendingLogin => null;
Future<LoginOutcome> login({
required String email,
required String password,
required String locale,
}) async {
_setLoading(true);
try {
final result = await _accountsService.login(email, password, locale: locale);
_account = result.account;
_error = null;
await _permissionsProvider.loadForAccount(result.account.id);
return LoginOutcome.completed(result.account);
} catch (e) {
_error = e;
rethrow;
} finally {
_setLoading(false);
}
}
void completePendingLogin(Account account) {
_account = account;
_permissionsProvider.loadForAccount(account.id);
notifyListeners();
}
Future<Account?> restore() async {
_setLoading(true);
_account = null;
_permissionsProvider.clear();
_error = Exception('Сохраненная сессия отсутствует');
_setLoading(false);
return null;
}
Future<void> signup({
required AccountData account,
required Describable organization,
required String timezone,
required Describable ownerRole,
}) async {
_setLoading(true);
_error = null;
try {
await _accountsService.signup(account);
} catch (e) {
_error = e;
rethrow;
} finally {
_setLoading(false);
}
}
Future<void> logout() async {
_account = null;
_error = null;
_permissionsProvider.clear();
notifyListeners();
}
void _setLoading(bool value) {
_isLoading = value;
notifyListeners();
}
}

View File

@@ -0,0 +1,82 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:pshared/models/permissions/action.dart' as perm;
import 'package:pshared/models/permissions/data/permission.dart';
import 'package:pshared/models/permissions/descriptions/policy.dart';
import 'package:pshared/models/permissions/effect.dart';
import 'package:pshared/models/resources.dart';
import 'package:pweb/services/permissions.dart';
import 'package:pweb/services/mock_ids.dart';
class PermissionsProvider extends ChangeNotifier {
final PermissionsService _service;
PermissionsProvider({required PermissionsService service}) : _service = service;
bool _isLoading = false;
Object? _error;
String? _accountRef;
bool _hasLoaded = false;
String? _roleRef;
List<Permission> _permissions = [];
List<PolicyDescription> _policyDescriptions = [];
bool get isLoading => _isLoading;
Object? get error => _error;
bool get isReady => _hasLoaded && !_isLoading && _error == null;
List<Permission> get permissions => List.unmodifiable(_permissions);
bool get hasLoaded => _hasLoaded;
bool get isCompany => _roleRef == companyRoleId;
bool get isRecipient => _roleRef == recipientRoleId;
Future<void> loadForAccount(String accountRef) async {
_accountRef = accountRef;
_isLoading = true;
_error = null;
notifyListeners();
try {
final access = await _service.loadForAccount(accountRef);
_permissions = access.permissions.permissions;
_policyDescriptions = access.descriptions.policies;
_roleRef = access.permissions.roles.firstOrNull?.descriptionRef;
} catch (e) {
_permissions = [];
_policyDescriptions = [];
_error = e;
_roleRef = null;
} finally {
_hasLoaded = true;
_isLoading = false;
notifyListeners();
}
}
void clear() {
_accountRef = null;
_permissions = [];
_policyDescriptions = [];
_error = null;
_hasLoaded = false;
_roleRef = null;
notifyListeners();
}
bool canAccessResource(ResourceType resource, {perm.Action? action}) {
final policy = _policyDescriptions.firstWhereOrNull(
(policy) => (policy.resourceTypes?.contains(resource) ?? false),
);
if (policy == null) return false;
return _permissions.any(
(permission) =>
permission.accountRef == _accountRef &&
permission.policy.descriptionRef == policy.storable.id &&
permission.policy.effect.effect == Effect.allow &&
(action == null || permission.policy.effect.action == action),
);
}
}

View File

@@ -1,15 +1,13 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:pshared/models/auth/pending_login.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/service/account.dart';
import 'package:pweb/services/auth.dart';
class TwoFactorProvider extends ChangeNotifier {
static final _logger = Logger('provider.two_factor');
final AccountProvider _accountProvider;
final AuthenticationService _authService;
TwoFactorProvider({required AccountProvider accountProvider}) : _accountProvider = accountProvider;
TwoFactorProvider({AuthenticationService? authService}) : _authService = authService ?? AuthenticationService();
bool _isSubmitting = false;
bool _hasError = false;
@@ -20,7 +18,6 @@ class TwoFactorProvider extends ChangeNotifier {
bool get hasError => _hasError;
bool get verificationSuccess => _verificationSuccess;
String? get errorMessage => _errorMessage;
PendingLogin? get pendingLogin => _accountProvider.pendingLogin;
Future<void> submitCode(String code) async {
@@ -31,16 +28,8 @@ class TwoFactorProvider extends ChangeNotifier {
notifyListeners();
try {
final pending = _accountProvider.pendingLogin;
if (pending == null) {
throw Exception('No pending login available');
}
final account = await AccountService.confirmLoginCode(
pending: pending,
code: code,
);
_accountProvider.completePendingLogin(account);
_verificationSuccess = true;
final isValid = await _authService.verifyTwoFactorCode(code);
_verificationSuccess = isValid;
} catch (e) {
_hasError = true;
_errorMessage = e.toString();
@@ -52,18 +41,9 @@ class TwoFactorProvider extends ChangeNotifier {
}
Future<void> resendCode() async {
final pending = _accountProvider.pendingLogin;
if (pending == null) {
_logger.warning('No pending login to resend code for');
return;
}
try {
await AccountService.resendLoginCode(pending);
} catch (e) {
_logger.warning('Failed to resend login code', e);
_hasError = true;
_errorMessage = e.toString();
notifyListeners();
}
_logger.fine('Resending mock two-factor code');
_hasError = false;
_errorMessage = null;
notifyListeners();
}
}