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 {}"
},
"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."
}
}

View File

@ -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": "Этот ключ уже добавлен."
}

View File

@ -18,7 +18,7 @@ class ApiResponse<D> {
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<ApiResponse<User>> 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<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(
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);
}
}

View File

@ -12,9 +12,6 @@ class BackblazeFormCubit extends FormCubit {
initalValue: '',
validations: [
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: '',
validations: [
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: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<String>(
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LengthStringValidationWithLengthShowing(
40,
'validations.length'.tr(
args: ["40"],
),
)
LengthStringNotEqualValidation(40)
],
);

View File

@ -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<String>(
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LengthStringValidationWithLengthShowing(
64, 'validations.length'.tr(args: ["64"]))
LengthStringNotEqualValidation(64)
],
);

View File

@ -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<String>(
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
LengthStringLongerValidation(userMaxLength),
ValidationModel<String>((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<String>((s) => passwordRegExp.hasMatch(s),
ValidationModel<String>((s) => passwordForbiddenRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);

View File

@ -1,13 +1,28 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart';
class LengthStringValidationWithLengthShowing extends ValidationModel<String> {
LengthStringValidationWithLengthShowing(int length, String errorText)
: super((n) => n.length != length, errorText);
abstract class LengthStringValidation extends ValidationModel<String> {
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()]));
}

View File

@ -160,7 +160,12 @@ class UsersCubit extends AppConfigDependendCubit<UsersState> {
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<User>.from(state.users);
loadedUsers.add(result.data);
await box.clear();