Handle reset password + verify user account properly (new tenant) + removed check of strong password (dups) + Fixed Unit Tests
MODIFY
src/assets/server/rest/v1/schemas/auth/auth-reset-password.json
src/assets/server/rest/v1/schemas/auth/auth-reset-password.json
"repeatPassword": {
"$ref": "common.json#/definitions/password"
}
- }
+ },
+ "required": ["password", "repeatPassword"]
}
},
"required": [
- "email",
- "tenant"
+ "tenant",
+ "captcha"
],
"anyOf": [
- { "required": ["captcha"] },
+ { "required": ["email"] },
{ "required": ["hash"] }
- ]
+ ],
+ "dependencies": {
+ "hash": { "required": ["passwords"] }
+ }
}

MODIFY
src/server/rest/v1/service/AuthService.ts
src/server/rest/v1/service/AuthService.ts
@@ -60,38 +60,7 @@ export default class AuthService {
public static async handleLogIn(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- let filteredRequest: Partial<HttpLoginRequest>;
- try {
- filteredRequest = AuthValidator.getInstance().validateAuthSignIn(req.body);
- } catch (e) {
- if (!req.body.email) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The Email is mandatory',
- module: MODULE_NAME,
- method: 'handleLogIn'
- });
- }
- if (!req.body.password) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The Password is mandatory',
- module: MODULE_NAME,
- method: 'handleLogIn'
- });
- }
- if (!req.body.acceptEula) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.USER_EULA_ERROR,
- message: 'The End-user License Agreement is mandatory',
- module: MODULE_NAME,
- method: 'handleLogIn'
- });
- }
- }
+ const filteredRequest = AuthValidator.getInstance().validateAuthSignIn(req.body);
// Get Tenant
const tenantID = await AuthService.getTenantID(filteredRequest.tenant);
if (!tenantID) {
@@ -164,31 +133,8 @@ export default class AuthService {
public static async handleRegisterUser(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- let filteredRequest: Partial<HttpRegisterUserRequest>;
- try {
- filteredRequest = AuthValidator.getInstance().validateAuthSignOn(req.body);
- } catch (e) {
- // Check EULA
- if (!req.body.acceptEula) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.USER_EULA_ERROR,
- message: 'The End-user License Agreement is mandatory',
- module: MODULE_NAME,
- method: 'handleRegisterUser'
- });
- }
- // Check
- if (!req.body.captcha) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The captcha is mandatory',
- module: MODULE_NAME,
- method: 'handleRegisterUser'
- });
- }
- }
+ const filteredRequest = AuthValidator.getInstance().validateAuthSignOn(req.body);
+ // Override
filteredRequest.status = UserStatus.PENDING;
if (!filteredRequest.locale) {
filteredRequest.locale = Constants.DEFAULT_LOCALE;
@@ -422,8 +368,19 @@ export default class AuthService {
method: 'handleUserPasswordReset'
});
}
+ // Check password
+ if (!Utils.isPasswordValid(filteredRequest.passwords.password)) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.OBJECT_DOES_NOT_EXIST_ERROR,
+ message: `Password with reset hash '${filteredRequest.hash}' is not strong enough`,
+ module: MODULE_NAME,
+ user: user,
+ method: 'handleUserPasswordReset'
+ });
+ }
// Hash it
- const newHashedPassword = await Utils.hashPasswordBcrypt(filteredRequest.password);
+ const newHashedPassword = await Utils.hashPasswordBcrypt(filteredRequest.passwords.password);
// Save new password
await UserStorage.saveUserPassword(tenantID, user.id,
{
@@ -446,35 +403,13 @@ export default class AuthService {
message: 'User\'s password has been reset successfully',
detailedMessages: { params: req.body }
});
-
// Ok
res.json(Constants.REST_RESPONSE_SUCCESS);
next();
}
public static async handleUserPasswordReset(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- let filteredRequest: Partial<HttpResetPasswordRequest>;
- try {
- filteredRequest = AuthValidator.getInstance().validateAuthResetPassword(req.body);
- } catch (e) {
- if (!req.body.email) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The E-mail is mandatory',
- module: MODULE_NAME,
- method: 'handleUserPasswordReset'
- });
- } else if (!req.body.captcha && !req.body.hash) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The Captcha or the Hash is mandatory',
- module: MODULE_NAME,
- method: 'handleUserPasswordReset'
- });
- }
- }
+ const filteredRequest = AuthValidator.getInstance().validateAuthResetPassword(req.body);
// Get Tenant
const tenantID = await AuthService.getTenantID(filteredRequest.tenant);
if (!tenantID) {
@@ -488,41 +423,18 @@ export default class AuthService {
});
}
// Check hash
- if (!filteredRequest.hash) {
- // Send Confirmation Email for requesting a new password
- await AuthService.checkAndSendResetPasswordConfirmationEmail(tenantID, filteredRequest, action, req, res, next);
- } else {
+ if (filteredRequest.hash) {
// Send the new password
await AuthService.resetUserPassword(tenantID, filteredRequest, action, req, res, next);
+ } else {
+ // Send Confirmation Email for requesting a new password
+ await AuthService.checkAndSendResetPasswordConfirmationEmail(tenantID, filteredRequest, action, req, res, next);
}
}
public static async handleCheckEndUserLicenseAgreement(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- let filteredRequest: Partial<HttpCheckEulaRequest>;
- try {
- filteredRequest = AuthValidator.getInstance().validateAuthCheckEula(req.query);
- } catch (e) {
- // Check
- if (!req.body.Tenant) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The Tenant is mandatory',
- module: MODULE_NAME,
- method: 'handleCheckEndUserLicenseAgreement'
- });
- }
- if (!req.body.Email) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The Email is mandatory',
- module: MODULE_NAME,
- method: 'handleCheckEndUserLicenseAgreement'
- });
- }
- }
+ const filteredRequest = AuthValidator.getInstance().validateAuthCheckEula(req.query);
// Get Tenant
const tenantID = await AuthService.getTenantID(filteredRequest.Tenant);
if (!tenantID) {
@@ -566,33 +478,7 @@ export default class AuthService {
public static async handleVerifyEmail(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- let filteredRequest;
- try {
- filteredRequest = AuthValidator.getInstance().validateAuthVerifyEmail(req.body);
- } catch (e) {
- // Check email
- if (!req.body.Email) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- action: action,
- module: MODULE_NAME,
- method: 'handleVerifyEmail',
- message: 'The email is mandatory'
- });
- }
- // Check verificationToken
- if (!req.body.VerificationToken) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- action: action,
- module: MODULE_NAME,
- method: 'handleVerifyEmail',
- message: 'Verification token is mandatory'
- });
- }
- }
+ const filteredRequest = AuthValidator.getInstance().validateAuthVerifyEmail(req.query);
// Get Tenant
const tenantID = await AuthService.getTenantID(filteredRequest.Tenant);
if (!tenantID) {
@@ -694,34 +580,7 @@ export default class AuthService {
}
public static async handleResendVerificationEmail(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- // Filter
- let filteredRequest;
- try {
- filteredRequest = AuthValidator.getInstance().validateAuthResendVerificationEmail(req.body);
- } catch (e) {
- // Check email
- if (!filteredRequest.email) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The Email is mandatory',
- module: MODULE_NAME,
- method: 'handleResendVerificationEmail',
- action: action
- });
- }
- // Check captcha
- if (!filteredRequest.captcha) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'The captcha is mandatory',
- module: MODULE_NAME,
- method: 'handleResendVerificationEmail',
- action: action
- });
- }
- }
+ const filteredRequest = AuthValidator.getInstance().validateAuthResendVerificationEmail(req.body);
// Get the tenant
const tenantID = await AuthService.getTenantID(filteredRequest.tenant);
if (!tenantID) {

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -1115,18 +1115,6 @@ export default class Utils {
});
}
- public static isPasswordStrongEnough(password: string): boolean {
- const uc = password.match(Constants.PWD_UPPERCASE_RE);
- const lc = password.match(Constants.PWD_LOWERCASE_RE);
- const n = password.match(Constants.PWD_NUMBER_RE);
- const sc = password.match(Constants.PWD_SPECIAL_CHAR_RE);
- return password.length >= Constants.PWD_MIN_LENGTH &&
- uc && uc.length >= Constants.PWD_UPPERCASE_MIN_COUNT &&
- lc && lc.length >= Constants.PWD_LOWERCASE_MIN_COUNT &&
- n && n.length >= Constants.PWD_NUMBER_MIN_COUNT &&
- sc && sc.length >= Constants.PWD_SPECIAL_MIN_COUNT;
- }
-
public static containsAddressGPSCoordinates(address: Address): boolean {
// Check if GPS are available
if (address && Utils.containsGPSCoordinates(address.coordinates)) {
@@ -1150,7 +1138,7 @@ export default class Utils {
public static generatePassword(): string {
let password = '';
const randomLength = Utils.getRandomInt(Constants.PWD_MAX_LENGTH, Constants.PWD_MIN_LENGTH);
- while (!Utils.isPasswordStrongEnough(password)) {
+ while (!Utils.isPasswordValid(password)) {
// eslint-disable-next-line no-useless-escape
password = passwordGenerator(randomLength, false, /[\w\d!#\$%\^&\*\.\?\-]/);
}

MODIFY
test/api/AuthenticationTest.ts
test/api/AuthenticationTest.ts
@@ -251,7 +251,7 @@ describe('Authentication Service (tenant utall)', function() {
const response = await CentralServerService.defaultInstance.authenticationApi.login(
testData.adminEmail, testData.adminPassword, false, testData.adminTenant);
// Check
- expect(response.status).to.be.eql(520);
+ expect(response.status).to.be.eql(500);
expect(response.data).to.not.have.property('token');
});
@@ -260,7 +260,7 @@ describe('Authentication Service (tenant utall)', function() {
const response = await CentralServerService.defaultInstance.authenticationApi.login(
testData.adminEmail, testData.adminPassword, null, testData.adminTenant);
// Check
- expect(response.status).to.be.eql(520);
+ expect(response.status).to.be.eql(500);
expect(response.data).to.not.have.property('token');
});

MODIFY
test/api/client/AuthenticationApi.ts
test/api/client/AuthenticationApi.ts
+import BaseApi from './utils/BaseApi';
import { ServerAction } from '../../../src/types/Server';
+import User from '../../../src/types/User';
export default class AuthenticationApi {
- private _baseApi: any;
+ private _baseApi: BaseApi;
public constructor(baseApi) {
this._baseApi = baseApi;
}
- public async login(email, password, acceptEula = true, tenant = '') {
+ public async login(email: string, password: string, acceptEula = true, tenant = '') {
const data: any = {};
// Allow caller to not pass param for the tests
if (email) {
@@ -36,10 +38,7 @@ export default class AuthenticationApi {
return response;
}
- async registerUser(user, tenant = null) {
- if (tenant) {
- user.tenant = tenant;
- }
+ async registerUser(user: User, tenant = null) {
// Send
const response = await this._baseApi.send({
method: 'POST',
@@ -47,12 +46,15 @@ export default class AuthenticationApi {
headers: {
'Content-Type': 'application/json'
},
- data: user
+ data: {
+ ...user,
+ tenant
+ }
});
return response;
}
- public async resetUserPassword(email, tenant = '') {
+ public async resetUserPassword(email: string, tenant = '') {
const data = {
email: email,
tenant: tenant,
@@ -70,7 +72,7 @@ export default class AuthenticationApi {
return response;
}
- public async resendVerificationEmail(email, tenant = '') {
+ public async resendVerificationEmail(email: string, tenant = '') {
const data = {
email: email,
tenant: tenant,
@@ -88,20 +90,14 @@ export default class AuthenticationApi {
return response;
}
- public async verifyEmail(email, verificationToken, tenant = '') {
- const data = {
- Email: email,
- Tenant: tenant,
- VerificationToken: verificationToken
- };
+ public async verifyEmail(email: string, verificationToken: string, tenant = '') {
// Send
const response = await this._baseApi.send({
method: 'GET',
- url: '/v1/auth/' + ServerAction.REST_MAIL_CHECK,
+ url: `/v1/auth/${ServerAction.REST_MAIL_CHECK}?Email=${email}&Tenant=${tenant}&VerificationToken=${verificationToken}`,
headers: {
'Content-Type': 'application/json'
- },
- data: data
+ }
});
return response;
}

MODIFY
test/api/client/utils/BaseApi.ts
test/api/client/utils/BaseApi.ts
@@ -25,15 +25,20 @@ export default class BaseApi {
let t1 = 0;
try {
t0 = performance.now();
- // Execute with Axios
- httpResponse = await axiosInstance(httpRequest);
// Debug
- if (config.trace_logs) {
- console.debug('HTTP Request ====================================');
+ if (config.get('trace_logs')) {
+ console.debug('HTTP Request =======================');
console.debug(httpRequest.baseURL);
console.debug(httpRequest.url);
console.debug(httpRequest.method);
console.debug(httpRequest.data);
+ console.debug('====================================');
+ }
+ // Execute with Axios
+ httpResponse = await axiosInstance(httpRequest);
+ // Debug
+ if (config.get('trace_logs')) {
+ console.debug('HTTP Response ======================');
console.debug(httpResponse.status);
console.debug(httpResponse.statusText);
console.debug(httpResponse.data);

Handle reset password + verify user account properly (new tenant) + removed check of strong password (dups) + Fixed Unit Tests