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
This commit is contained in:
NaiJi 2022-05-02 14:56:46 +03:00
parent 265cc15ea5
commit 4c99579f13
9 changed files with 95 additions and 69 deletions

View File

@ -320,12 +320,13 @@
"delete_ssh_key": "Delete SSH key for {}" "delete_ssh_key": "Delete SSH key for {}"
}, },
"validations": { "validations": {
"required": "Required", "required": "Required.",
"invalid_format": "Invalid format", "invalid_format": "Invalid format.",
"root_name": "User name cannot be 'root'", "root_name": "User name cannot be 'root'.",
"key_format": "Invalid key format", "key_format": "Invalid key format.",
"length": "Length is [] should be {}", "length_not_equal": "Length is []. Should be {}.",
"user_already_exist": "Already exists", "length_longer": "Length is []. Should be shorter than or equal to {}.",
"key_already_exists": "This key already exists" "user_already_exist": "This user already exists.",
"key_already_exists": "This key already exists."
} }
} }

View File

@ -323,9 +323,10 @@
"validations": { "validations": {
"required": "Обязательное поле.", "required": "Обязательное поле.",
"invalid_format": "Неверный формат.", "invalid_format": "Неверный формат.",
"root_name": "Имя пользователя не может быть'root'.", "root_name": "Имя пользователя не может быть 'root'.",
"key_format": "Неверный формат.", "key_format": "Неверный формат.",
"length": "Длина строки [] должна быть {}.", "length_not_equal": "Длина строки []. Должно быть равно {}.",
"length_longer": "Длина строки []. Должно быть меньше либо равно {}.",
"user_already_exist": "Имя уже используется.", "user_already_exist": "Имя уже используется.",
"key_already_exists": "Этот ключ уже добавлен." "key_already_exists": "Этот ключ уже добавлен."
} }

View File

@ -18,7 +18,7 @@ class ApiResponse<D> {
final String? errorMessage; final String? errorMessage;
final D data; final D data;
get isSuccess => statusCode >= 200 && statusCode < 300; bool get isSuccess => statusCode >= 200 && statusCode < 300;
ApiResponse({ ApiResponse({
required this.statusCode, required this.statusCode,
@ -65,27 +65,48 @@ class ServerApi extends ApiMap {
} }
Future<ApiResponse<User>> createUser(User user) async { Future<ApiResponse<User>> createUser(User user) async {
Response response;
var client = await getClient(); var client = await getClient();
// POST request with JSON body containing username and password // POST request with JSON body containing username and password
response = await client.post( var makeErrorApiReponse = (int status) {
'/users',
data: {
'username': user.login,
'password': user.password,
},
options: Options(
contentType: 'application/json',
),
);
close(client);
if (response.statusCode == HttpStatus.created) {
return ApiResponse( return ApiResponse(
statusCode: response.statusCode ?? HttpStatus.internalServerError, statusCode: status,
data: User(
login: user.login,
password: user.password,
isFoundOnServer: false,
),
);
};
late Response<dynamic> 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( data: User(
login: user.login, login: user.login,
password: user.password, password: user.password,
@ -93,18 +114,11 @@ class ServerApi extends ApiMap {
), ),
); );
} else { } else {
return ApiResponse( print(response.statusCode.toString() +
statusCode: response.statusCode ?? HttpStatus.internalServerError, ": " +
data: User( (response.statusMessage ?? ""));
login: user.login, return makeErrorApiReponse(
password: user.password, response.statusCode ?? HttpStatus.internalServerError);
isFoundOnServer: false,
note: response.data['message'] ?? null,
),
errorMessage: response.data?.containsKey('error') ?? false
? response.data['error']
: null,
);
} }
} }

View File

@ -12,9 +12,6 @@ class BackblazeFormCubit extends FormCubit {
initalValue: '', initalValue: '',
validations: [ validations: [
RequiredStringValidation('validations.required'.tr()), RequiredStringValidation('validations.required'.tr()),
//ValidationModel<String>(
//(s) => regExp.hasMatch(s), 'invalid key format'),
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
], ],
); );
@ -22,9 +19,6 @@ class BackblazeFormCubit extends FormCubit {
initalValue: '', initalValue: '',
validations: [ validations: [
RequiredStringValidation('required'), RequiredStringValidation('required'),
//ValidationModel<String>(
//(s) => regExp.hasMatch(s), 'invalid key format'),
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
], ],
); );

View File

@ -4,8 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart'; import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import '../validations/validations.dart';
class CloudFlareFormCubit extends FormCubit { class CloudFlareFormCubit extends FormCubit {
CloudFlareFormCubit(this.initializingCubit) { CloudFlareFormCubit(this.initializingCubit) {
@ -16,12 +15,7 @@ class CloudFlareFormCubit extends FormCubit {
RequiredStringValidation('validations.required'.tr()), RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>( ValidationModel<String>(
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()), (s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LengthStringValidationWithLengthShowing( LengthStringNotEqualValidation(40)
40,
'validations.length'.tr(
args: ["40"],
),
)
], ],
); );

View File

@ -4,8 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import '../validations/validations.dart';
class HetznerFormCubit extends FormCubit { class HetznerFormCubit extends FormCubit {
HetznerFormCubit(this.initializingCubit) { HetznerFormCubit(this.initializingCubit) {
@ -16,8 +15,7 @@ class HetznerFormCubit extends FormCubit {
RequiredStringValidation('validations.required'.tr()), RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>( ValidationModel<String>(
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()), (s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LengthStringValidationWithLengthShowing( LengthStringNotEqualValidation(64)
64, 'validations.length'.tr(args: ["64"]))
], ],
); );

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.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/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/logic/models/user.dart';
@ -15,8 +16,10 @@ class UserFormCubit extends FormCubit {
}) { }) {
var isEdit = user != null; var isEdit = user != null;
var userRegExp = RegExp(r"\W"); var userAllowedRegExp = RegExp(r"^[a-z_][a-z0-9_]+$");
var passwordRegExp = RegExp(r"[\n\r\s]+"); const userMaxLength = 31;
var passwordForbiddenRegExp = RegExp(r"[\n\r\s]+");
login = FieldCubit( login = FieldCubit(
initalValue: isEdit ? user!.login : '', initalValue: isEdit ? user!.login : '',
@ -28,8 +31,9 @@ class UserFormCubit extends FormCubit {
'validations.user_already_exist'.tr(), 'validations.user_already_exist'.tr(),
), ),
RequiredStringValidation('validations.required'.tr()), RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>( LengthStringLongerValidation(userMaxLength),
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()), ValidationModel<String>((s) => !userAllowedRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
], ],
); );
@ -38,7 +42,7 @@ class UserFormCubit extends FormCubit {
isEdit ? (user?.password ?? '') : StringGenerators.userPassword(), isEdit ? (user?.password ?? '') : StringGenerators.userPassword(),
validations: [ validations: [
RequiredStringValidation('validations.required'.tr()), RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>((s) => passwordRegExp.hasMatch(s), ValidationModel<String>((s) => passwordForbiddenRegExp.hasMatch(s),
'validations.invalid_format'.tr()), 'validations.invalid_format'.tr()),
], ],
); );

View File

@ -1,13 +1,28 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart';
class LengthStringValidationWithLengthShowing extends ValidationModel<String> { abstract class LengthStringValidation extends ValidationModel<String> {
LengthStringValidationWithLengthShowing(int length, String errorText) LengthStringValidation(bool Function(String) predicate, String errorMessage)
: super((n) => n.length != length, errorText); : super(predicate, errorMessage);
@override @override
String? check(String val) { String? check(String value) {
var length = val.length; var length = value.length;
var errorMassage = this.errorMassage.replaceAll("[]", length.toString()); var errorMessage = this.errorMassage.replaceAll("[]", length.toString());
return test(val) ? errorMassage : null; 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()]));
}

View File

@ -160,7 +160,12 @@ class UsersCubit extends AppConfigDependendCubit<UsersState> {
if (user.login == 'root' || user.login == state.primaryUser.login) { if (user.login == 'root' || user.login == state.primaryUser.login) {
return; return;
} }
// If API returned error, do nothing
final result = await api.createUser(user); final result = await api.createUser(user);
if (!result.isSuccess) {
return;
}
var loadedUsers = List<User>.from(state.users); var loadedUsers = List<User>.from(state.users);
loadedUsers.add(result.data); loadedUsers.add(result.data);
await box.clear(); await box.clear();