From 4c99579f13af3ee5e3678fa27289ce4a7cd4f0ad Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 2 May 2022 14:56:46 +0300 Subject: [PATCH] Fix username validation and exception handling 1. Refactor string validation classes 2. Rename string validation assets for length 3. Improve exception handling when server is not able to create requested server --- assets/translations/en.json | 15 ++-- assets/translations/ru.json | 5 +- lib/logic/api_maps/server.dart | 74 +++++++++++-------- .../initializing/backblaze_form_cubit.dart | 6 -- .../initializing/cloudflare_form_cubit.dart | 10 +-- .../initializing/hetzner_form_cubit.dart | 6 +- .../cubit/forms/user/user_form_cubit.dart | 14 ++-- .../cubit/forms/validations/validations.dart | 29 ++++++-- lib/logic/cubit/users/users_cubit.dart | 5 ++ 9 files changed, 95 insertions(+), 69 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 45faa3e8..73c3575e 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -320,12 +320,13 @@ "delete_ssh_key": "Delete SSH key for {}" }, "validations": { - "required": "Required", - "invalid_format": "Invalid format", - "root_name": "User name cannot be 'root'", - "key_format": "Invalid key format", - "length": "Length is [] should be {}", - "user_already_exist": "Already exists", - "key_already_exists": "This key already exists" + "required": "Required.", + "invalid_format": "Invalid format.", + "root_name": "User name cannot be 'root'.", + "key_format": "Invalid key format.", + "length_not_equal": "Length is []. Should be {}.", + "length_longer": "Length is []. Should be shorter than or equal to {}.", + "user_already_exist": "This user already exists.", + "key_already_exists": "This key already exists." } } diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 619096a7..ee085ed4 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -323,9 +323,10 @@ "validations": { "required": "Обязательное поле.", "invalid_format": "Неверный формат.", - "root_name": "Имя пользователя не может быть'root'.", + "root_name": "Имя пользователя не может быть 'root'.", "key_format": "Неверный формат.", - "length": "Длина строки [] должна быть {}.", + "length_not_equal": "Длина строки []. Должно быть равно {}.", + "length_longer": "Длина строки []. Должно быть меньше либо равно {}.", "user_already_exist": "Имя уже используется.", "key_already_exists": "Этот ключ уже добавлен." } diff --git a/lib/logic/api_maps/server.dart b/lib/logic/api_maps/server.dart index 0e8930e5..1d2c3bce 100644 --- a/lib/logic/api_maps/server.dart +++ b/lib/logic/api_maps/server.dart @@ -18,7 +18,7 @@ class ApiResponse { final String? errorMessage; final D data; - get isSuccess => statusCode >= 200 && statusCode < 300; + bool get isSuccess => statusCode >= 200 && statusCode < 300; ApiResponse({ required this.statusCode, @@ -65,27 +65,48 @@ class ServerApi extends ApiMap { } Future> createUser(User user) async { - Response response; - var client = await getClient(); // POST request with JSON body containing username and password - response = await client.post( - '/users', - data: { - 'username': user.login, - 'password': user.password, - }, - options: Options( - contentType: 'application/json', - ), - ); - - close(client); - - if (response.statusCode == HttpStatus.created) { + var makeErrorApiReponse = (int status) { return ApiResponse( - statusCode: response.statusCode ?? HttpStatus.internalServerError, + statusCode: status, + data: User( + login: user.login, + password: user.password, + isFoundOnServer: false, + ), + ); + }; + + late Response response; + + try { + response = await client.post( + '/users', + data: { + 'username': user.login, + 'password': user.password, + }, + options: Options( + contentType: 'application/json', + receiveDataWhenStatusError: true, + followRedirects: false, + validateStatus: (status) { + return (status != null) && + (status < HttpStatus.internalServerError); + }), + ); + } catch (e) { + return makeErrorApiReponse(HttpStatus.internalServerError); + } finally { + close(client); + } + + if ((response.statusCode != null) && + (response.statusCode == HttpStatus.created)) { + return ApiResponse( + statusCode: response.statusCode!, data: User( login: user.login, password: user.password, @@ -93,18 +114,11 @@ class ServerApi extends ApiMap { ), ); } else { - return ApiResponse( - statusCode: response.statusCode ?? HttpStatus.internalServerError, - data: User( - login: user.login, - password: user.password, - isFoundOnServer: false, - note: response.data['message'] ?? null, - ), - errorMessage: response.data?.containsKey('error') ?? false - ? response.data['error'] - : null, - ); + print(response.statusCode.toString() + + ": " + + (response.statusMessage ?? "")); + return makeErrorApiReponse( + response.statusCode ?? HttpStatus.internalServerError); } } diff --git a/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart b/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart index eda06939..d8777fa8 100644 --- a/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart +++ b/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart @@ -12,9 +12,6 @@ class BackblazeFormCubit extends FormCubit { initalValue: '', validations: [ RequiredStringValidation('validations.required'.tr()), - //ValidationModel( - //(s) => regExp.hasMatch(s), 'invalid key format'), - //LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64') ], ); @@ -22,9 +19,6 @@ class BackblazeFormCubit extends FormCubit { initalValue: '', validations: [ RequiredStringValidation('required'), - //ValidationModel( - //(s) => regExp.hasMatch(s), 'invalid key format'), - //LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64') ], ); diff --git a/lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart b/lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart index 7ee8c8fa..d811843b 100644 --- a/lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart +++ b/lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart @@ -4,8 +4,7 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/logic/api_maps/cloudflare.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; - -import '../validations/validations.dart'; +import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class CloudFlareFormCubit extends FormCubit { CloudFlareFormCubit(this.initializingCubit) { @@ -16,12 +15,7 @@ class CloudFlareFormCubit extends FormCubit { RequiredStringValidation('validations.required'.tr()), ValidationModel( (s) => regExp.hasMatch(s), 'validations.key_format'.tr()), - LengthStringValidationWithLengthShowing( - 40, - 'validations.length'.tr( - args: ["40"], - ), - ) + LengthStringNotEqualValidation(40) ], ); diff --git a/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart b/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart index 55af50d9..ce3e5aa9 100644 --- a/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart +++ b/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart @@ -4,8 +4,7 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/logic/api_maps/hetzner.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; - -import '../validations/validations.dart'; +import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class HetznerFormCubit extends FormCubit { HetznerFormCubit(this.initializingCubit) { @@ -16,8 +15,7 @@ class HetznerFormCubit extends FormCubit { RequiredStringValidation('validations.required'.tr()), ValidationModel( (s) => regExp.hasMatch(s), 'validations.key_format'.tr()), - LengthStringValidationWithLengthShowing( - 64, 'validations.length'.tr(args: ["64"])) + LengthStringNotEqualValidation(64) ], ); diff --git a/lib/logic/cubit/forms/user/user_form_cubit.dart b/lib/logic/cubit/forms/user/user_form_cubit.dart index 24f67437..4f0e6488 100644 --- a/lib/logic/cubit/forms/user/user_form_cubit.dart +++ b/lib/logic/cubit/forms/user/user_form_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/user.dart'; @@ -15,8 +16,10 @@ class UserFormCubit extends FormCubit { }) { var isEdit = user != null; - var userRegExp = RegExp(r"\W"); - var passwordRegExp = RegExp(r"[\n\r\s]+"); + var userAllowedRegExp = RegExp(r"^[a-z_][a-z0-9_]+$"); + const userMaxLength = 31; + + var passwordForbiddenRegExp = RegExp(r"[\n\r\s]+"); login = FieldCubit( initalValue: isEdit ? user!.login : '', @@ -28,8 +31,9 @@ class UserFormCubit extends FormCubit { 'validations.user_already_exist'.tr(), ), RequiredStringValidation('validations.required'.tr()), - ValidationModel( - (s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()), + LengthStringLongerValidation(userMaxLength), + ValidationModel((s) => !userAllowedRegExp.hasMatch(s), + 'validations.invalid_format'.tr()), ], ); @@ -38,7 +42,7 @@ class UserFormCubit extends FormCubit { isEdit ? (user?.password ?? '') : StringGenerators.userPassword(), validations: [ RequiredStringValidation('validations.required'.tr()), - ValidationModel((s) => passwordRegExp.hasMatch(s), + ValidationModel((s) => passwordForbiddenRegExp.hasMatch(s), 'validations.invalid_format'.tr()), ], ); diff --git a/lib/logic/cubit/forms/validations/validations.dart b/lib/logic/cubit/forms/validations/validations.dart index aff4ec92..34338559 100644 --- a/lib/logic/cubit/forms/validations/validations.dart +++ b/lib/logic/cubit/forms/validations/validations.dart @@ -1,13 +1,28 @@ import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; -class LengthStringValidationWithLengthShowing extends ValidationModel { - LengthStringValidationWithLengthShowing(int length, String errorText) - : super((n) => n.length != length, errorText); +abstract class LengthStringValidation extends ValidationModel { + LengthStringValidation(bool Function(String) predicate, String errorMessage) + : super(predicate, errorMessage); @override - String? check(String val) { - var length = val.length; - var errorMassage = this.errorMassage.replaceAll("[]", length.toString()); - return test(val) ? errorMassage : null; + String? check(String value) { + var length = value.length; + var errorMessage = this.errorMassage.replaceAll("[]", length.toString()); + return test(value) ? errorMessage : null; } } + +// String must be equal to [length] +class LengthStringNotEqualValidation extends LengthStringValidation { + LengthStringNotEqualValidation(int length) + : super((n) => n.length != length, + 'validations.length_not_equal'.tr(args: [length.toString()])); +} + +// String must be shorter than or equal to [length] +class LengthStringLongerValidation extends LengthStringValidation { + LengthStringLongerValidation(int length) + : super((n) => n.length > length, + 'validations.length_longer'.tr(args: [length.toString()])); +} diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index 65967a9a..0fd27064 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -160,7 +160,12 @@ class UsersCubit extends AppConfigDependendCubit { if (user.login == 'root' || user.login == state.primaryUser.login) { return; } + // If API returned error, do nothing final result = await api.createUser(user); + if (!result.isSuccess) { + return; + } + var loadedUsers = List.from(state.users); loadedUsers.add(result.data); await box.clear();