import 'package:comunic/helpers/preferences_helper.dart'; import 'package:comunic/helpers/push_notifications_helper.dart'; import 'package:comunic/helpers/websocket_helper.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/authentication_details.dart'; import 'package:comunic/models/new_account.dart'; import 'package:comunic/models/res_check_password_reset_token.dart'; /// Account helper /// /// @author Pierre HUBERT enum AuthResult { SUCCESS, TOO_MANY_ATTEMPTS, NETWORK_ERROR, INVALID_CREDENTIALS } enum CreateAccountResult { SUCCESS, ERROR_TOO_MANY_REQUESTS, ERROR_EXISTING_EMAIL, ERROR } class AccountHelper { // Current user ID static int? _currentUserID = -1; /// Checkout whether current user is signed in or not /// /// Warning : This method MUST BE CALLED AT LEAST ONCE AFTER APP START !!! Future signedIn() async { bool signedIn = (await PreferencesHelper.getInstance()).getLoginToken() != null; // Load current user ID for later use if (signedIn && _currentUserID == -1) await _loadCurrentUserID(); return signedIn; } /// Sign in user Future signIn(AuthenticationDetails auth) async { final response = await APIRequest.withoutLogin("account/login") .addString("mail", auth.email) .addString("password", auth.password) .exec(); // Handle errors if (response.code == 401) return AuthResult.INVALID_CREDENTIALS; else if (response.code == 429) return AuthResult.TOO_MANY_ATTEMPTS; else if (response.code != 200) return AuthResult.NETWORK_ERROR; // Save login token await (await PreferencesHelper.getInstance()) .setLoginToken(response.getObject()["token"]); // Get current user ID final userID = await _downloadCurrentUserID(); if (userID == null) { await signOut(); // We can not stay signed in without current user ID return AuthResult.NETWORK_ERROR; } // Save current user ID final preferences = await (PreferencesHelper.getInstance()); await preferences.setInt(PreferencesKeyList.USER_ID, userID); _currentUserID = userID; // Mark the tour as unseen await preferences.setBool(PreferencesKeyList.IS_TOUR_SEEN, false); return AuthResult.SUCCESS; } /// Sign out user Future signOut() async { await PushNotificationsHelper.clearLocalStatus(); await APIRequest.withLogin("account/logout").exec(); final preferencesHelper = await PreferencesHelper.getInstance(); await preferencesHelper.setLoginToken(null); await preferencesHelper.setInt(PreferencesKeyList.USER_ID, -1); _currentUserID = 0; // Close current web socket WebSocketHelper.close(); } /// Create a new user account Future createAccount(NewAccount info) async { final response = await APIRequest( uri: "account/create", needLogin: false, args: { "firstName": info.firstName, "lastName": info.lastName, "emailAddress": info.email, "password": info.password, }, ).exec(); switch (response.code) { case 200: return CreateAccountResult.SUCCESS; case 409: return CreateAccountResult.ERROR_EXISTING_EMAIL; case 429: return CreateAccountResult.ERROR_TOO_MANY_REQUESTS; default: return CreateAccountResult.ERROR; } } /// Check out whether a given email address exists or not /// /// Throws in case of failure static Future existsMailAccount(String email) async => (await APIRequest.withoutLogin("account/exists_email") .addString("email", email) .execWithThrow()) .getObject()["exists"]; /// Get current user email address static Future getCurrentAccountEmailAddress() async => (await APIRequest.withLogin("account/mail") .execWithThrowGetObject())["mail"]; /// Check out whether security questions have been set for an account or not /// /// Throws in case of failure static Future hasSecurityQuestions(String email) async => (await APIRequest.withoutLogin("account/has_security_questions") .addString("email", email) .execWithThrow()) .getObject()["defined"]; /// Get the security questions of the user /// /// Throws in case of failure static Future?> getSecurityQuestions(String? email) async => ((await APIRequest.withoutLogin("account/get_security_questions") .addString("email", email) .execWithThrow()) .getObject()["questions"]) .cast(); /// Validate given security answers /// /// Throws an [Exception] in case of failure /// /// Returns a password reset token in case of success static Future checkAnswers( String? email, List answers) async => (await APIRequest.withoutLogin("account/check_security_answers") .addString("email", email) .addString("answers", answers.map((f) => Uri.encodeComponent(f)).join("&")) .execWithThrow()) .getObject()["reset_token"]; /// Check a password reset token /// /// Throws in case failure static Future validatePasswordResetToken( String token) async { final response = await APIRequest.withoutLogin("account/check_password_reset_token") .addString("reset_token", token) .execWithThrowGetObject(); return ResCheckPasswordToken( firstName: response["first_name"], lastName: response["last_name"], email: response["mail"], ); } /// Change account password using password reset token /// /// Throws an exception in case of failure static Future changeAccountPassword( String token, String password) async => await APIRequest.withoutLogin("account/reset_user_passwd") .addString("reset_token", token) .addString("password", password) .execWithThrow(); /// Get current user ID from the server Future _downloadCurrentUserID() async { final response = await APIRequest.withLogin("account/id").exec(); if (response.code != 200) return null; return response.getObject()["userID"]; } /// Get the ID of the currently signed in user Future _loadCurrentUserID() async { final preferences = await PreferencesHelper.getInstance(); _currentUserID = preferences.getInt(PreferencesKeyList.USER_ID); } /// Check if current user ID is loaded or not static bool get isUserIDLoaded => _currentUserID! > 0; /// Get the ID of the currently signed in user static int? getCurrentUserID() { if (_currentUserID == -1) throw "Current user ID has not been loaded yet!"; return _currentUserID; } /// Disconnect all the devices of the current user /// /// Throws in case of failure static Future disconnectAllDevices() async { await APIRequest(uri: "account/disconnect_all_devices", needLogin: true) .execWithThrow(); } /// Remove permanently a user account /// /// Throws in case of failure static Future deleteAccount(String password) async { await APIRequest(uri: "account/delete", needLogin: true) .addString("password", password) .execWithThrow(); } }