199 lines
7.5 KiB
Dart
199 lines
7.5 KiB
Dart
// 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;
|
||
|
||
// Users can enter either their Keycloak username OR their Iran phone
|
||
// number. Phone gets swapped for the corresponding username here so
|
||
// every downstream call (getRequiredActions, OTP, Keycloak grant)
|
||
// sees a real username.
|
||
final resolved =
|
||
await KeycloakAuthService.instance.resolveIdentifier(username);
|
||
if (resolved == null) {
|
||
appState = AppState.failed;
|
||
ActionSheetUtils(navigatorKey.currentContext!)
|
||
.showAlert(AlertData(message: 'کاربر موجود نمیباشد.'));
|
||
return;
|
||
}
|
||
username = resolved;
|
||
|
||
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;
|
||
}
|
||
}
|