didvan-app/lib/views/authentication/authentication_state.dart

185 lines
6.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// lib/views/authentication/authentication_state.dart
//
// State مربوط به جریان احراز هویت. ساختار بیرونی این کلاس و امضای متدها
// عمدا بدون تغییر باقی مانده تا UI و screen ها به همان شکل قبل کار کنند.
// اما درون متدها به‌جای فراخوانی endpoint های قدیمی، از سرویس‌های جدید
// (KeycloakAuthService و AuthApiService) استفاده می‌کنیم.
import 'package:didvan/main.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/services/auth/auth_api_service.dart';
import 'package:didvan/services/auth/keycloak_auth_service.dart';
import 'package:didvan/services/auth/token_storage.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/foundation.dart';
import 'package:home_widget/home_widget.dart';
class AuthenticationState extends CoreProvier {
int _currentPageIndex = 0;
String username = '';
String password = '';
/// OTPای که در مرحله‌ی verify کاربر وارد می‌کند تا بعدا در reset-password
/// همراه newPassword به سرور فرستاده شود.
String _verificationCode = '';
bool isShowCustomDialog = true;
/// آیا کاربر روی Keycloak رمز فعال دارد؟
/// در مدل قدیمی: response /user/checkHasPassword
/// در مدل جدید: اگر `UPDATE_PASSWORD` در requiredActions باشد => false.
bool hasPassword = true;
set currentPageIndex(int value) {
_currentPageIndex = value;
notifyListeners();
}
int get currentPageIndex => _currentPageIndex;
/// مرحله‌ی اول: بررسی وجود کاربر و این که آیا رمز دارد یا نه.
Future<void> confirmUsername() async {
appState = AppState.isolatedBusy;
final result = await AuthApiService.instance.getRequiredActions(username);
if (result.isSuccess) {
final data = result.data ?? const <String, dynamic>{};
final actions =
(data['requiredActions'] as List?)?.cast<String>() ?? const [];
hasPassword = !actions.contains('UPDATE_PASSWORD');
appState = AppState.idle;
if (hasPassword) {
currentPageIndex = 1; // صفحه‌ی ورود رمز
} else {
// کاربر هنوز رمز ندارد => مستقیما به OTP می‌رویم تا تنظیم رمز انجام شود.
await sendOtpToken();
currentPageIndex = 2;
}
} else {
appState = AppState.failed;
String message = result.errorMessage ?? 'خطایی رخ داد.';
if (result.statusCode == 404) {
message = 'کاربر موجود نمی‌باشد.';
}
ActionSheetUtils(navigatorKey.currentContext!)
.showAlert(AlertData(message: message));
}
}
/// لاگین با username/password از طریق Keycloak.
/// در صورت موفقیت، توکن ذخیره شده و `getUserInfo` صدا زده می‌شود.
Future<String?> login(UserProvider userProvider) async {
appState = AppState.isolatedBusy;
final result = await KeycloakAuthService.instance.login(
username: username,
password: password,
);
if (result.success) {
final token = TokenStorage.accessToken;
// برای سازگاری با کد قدیمی که مستقیم با Storage کلید `token` کار می‌کند
// و home widget که توکن را برای rendering ویجت لازم دارد:
if (!kIsWeb && token != null) {
HomeWidget.saveWidgetData('token', token);
}
await userProvider.setAndGetToken(newToken: token);
await userProvider.getUserInfo();
appState = AppState.idle;
return token;
} else {
appState = AppState.failed;
ActionSheetUtils(navigatorKey.currentContext!).showAlert(
AlertData(message: result.errorMessage ?? 'خطا در ورود.'),
);
}
return null;
}
/// درخواست ارسال OTP برای reset password (و یا تنظیم رمز اولیه).
/// در صورت خطا یک alert به کاربر نشان می‌دهیم تا مشکل پنهان نماند.
Future<void> sendOtpToken() async {
if (kDebugMode) {
print('AuthenticationState: sendOtpToken for username="$username"');
}
final result = await AuthApiService.instance.generateOtp(username);
if (kDebugMode) {
print('AuthenticationState: generateOtp result -> '
'success=${result.isSuccess}, status=${result.statusCode}, '
'error=${result.errorMessage}');
}
if (!result.isSuccess) {
ActionSheetUtils(navigatorKey.currentContext!).showAlert(
AlertData(
message: result.errorMessage ??
'خطا در ارسال کد یک‌بار مصرف. لطفا دوباره تلاش کنید.',
),
);
}
}
/// تایید OTP. اگر معتبر بود، صفحه را به مرحله‌ی بعد (تنظیم رمز) می‌بریم.
/// `_verificationCode` نگه داشته می‌شود تا در `resetPassword`
/// همراه `newPassword` به سرور فرستاده شود (مدل جدید این دو را در یک
/// درخواست `reset-password-with-otp` ترکیب می‌کند).
Future<bool> verifyOtpToken(String token) async {
appState = AppState.isolatedBusy;
final result = await AuthApiService.instance.validateOtp(
username: username,
otp: token,
);
appState = AppState.idle;
if (result.isSuccess) {
_verificationCode = token;
currentPageIndex++;
return true;
}
ActionSheetUtils(navigatorKey.currentContext!).showAlert(
AlertData(
message: result.errorMessage ?? 'کد وارد شده صحیح نمی‌باشد',
),
);
return false;
}
/// تنظیم/تغییر رمز با استفاده از OTP تاییدشده.
Future<bool> resetPassword(String password) async {
if (_verificationCode.isEmpty) {
ActionSheetUtils(navigatorKey.currentContext!).showAlert(
AlertData(message: 'ابتدا کد یک‌بار مصرف را تایید کنید.'),
);
return false;
}
appState = AppState.isolatedBusy;
final result = await AuthApiService.instance.resetPasswordWithOtp(
username: username,
otp: _verificationCode,
newPassword: password,
);
appState = AppState.idle;
if (result.isSuccess) {
ActionSheetUtils(navigatorKey.currentContext!).showAlert(
AlertData(
message: 'کلمه عبور با موفقیت تغییر کرد',
aLertType: ALertType.success,
),
);
return true;
}
ActionSheetUtils(navigatorKey.currentContext!).showAlert(
AlertData(message: result.errorMessage ?? 'خطا در تغییر رمز.'),
);
return false;
}
}