import 'package:comunic/helpers/api_helper.dart'; import 'package:comunic/helpers/preferences_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/login_tokens.dart'; import 'package:comunic/models/new_account.dart'; import 'package:shared_preferences/shared_preferences.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 { static const _USER_ID_PREFERENCE_NAME = "user_id"; // 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()).getLoginTokens() != 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 request = APIRequest(uri: "account/login"); request.addString("userMail", auth.email); request.addString("userPassword", auth.password); final response = await APIHelper().exec(request); // 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 tokens final tokensObj = response.getObject()["tokens"]; await (await PreferencesHelper.getInstance()) .setLoginTokens(LoginTokens(tokensObj["token1"], tokensObj["token2"])); // 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 SharedPreferences.getInstance(); await preferences.setInt(_USER_ID_PREFERENCE_NAME, userID); _currentUserID = userID; return AuthResult.SUCCESS; } /// Sign out user Future signOut() async { await (await PreferencesHelper.getInstance()).setLoginTokens(null); _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"]; /// 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 => await APIRequest.withoutLogin("account/check_password_reset_token") .addString("token", token) .execWithThrow(); /// 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("token", token) .addString("password", password) .execWithThrow(); /// Get current user ID from the server Future _downloadCurrentUserID() async { final response = await APIRequest( uri: "user/getCurrentUserID", needLogin: true, ).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 SharedPreferences.getInstance(); _currentUserID = preferences.getInt(_USER_ID_PREFERENCE_NAME); } /// 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(); } }