diff --git a/CHANGELOG.md b/CHANGELOG.md index cec86f478..76527ac83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2328.01] - 2023-07-13 +## [2330.01] - 2023-07-27 ### Added @@ -14,6 +14,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +## [2328.01] - 2023-07-13 + +### Added +- (Clientes -> Morosos) Añadida columna "es trabajador" +- (Trabajadores -> Departamentos) Nueva sección +- (Trabajadores -> Departamentos) Añadido listado de Trabajadores por departamento +- (Trabajadores -> Departamentos) Añadido características de departamento e información + +### Changed + +### Fixed +- (Trabajadores -> Departamentos) Arreglado búscador + + ## [2326.01] - 2023-06-29 ### Added diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js new file mode 100644 index 000000000..73cc705de --- /dev/null +++ b/back/methods/vn-user/sign-in.js @@ -0,0 +1,102 @@ +const ForbiddenError = require('vn-loopback/util/forbiddenError'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('signIn', { + description: 'Login a user with username/email and password', + accepts: [ + { + arg: 'user', + type: 'String', + description: 'The user name or email', + required: true + }, { + arg: 'password', + type: 'String', + description: 'The password' + } + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/sign-in`, + verb: 'POST' + } + }); + + Self.signIn = async function(ctx, user, password, options) { + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const where = Self.userUses(user); + const vnUser = await Self.findOne({ + fields: ['id', 'name', 'password', 'active', 'email', 'passExpired', 'twoFactor'], + where + }, myOptions); + + const validCredentials = vnUser + && await vnUser.hasPassword(password); + + if (validCredentials) { + if (!vnUser.active) + throw new UserError('User disabled'); + await Self.sendTwoFactor(ctx, vnUser, myOptions); + await Self.passExpired(vnUser, myOptions); + + if (vnUser.twoFactor) + throw new ForbiddenError(null, 'REQUIRES_2FA'); + } + + return Self.validateLogin(user, password); + }; + + Self.passExpired = async(vnUser, myOptions) => { + const today = Date.vnNew(); + today.setHours(0, 0, 0, 0); + + if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { + const $ = Self.app.models; + const changePasswordToken = await $.AccessToken.create({ + scopes: ['changePassword'], + userId: vnUser.id + }, myOptions); + const err = new UserError('Pass expired', 'passExpired'); + changePasswordToken.twoFactor = vnUser.twoFactor ? true : false; + err.details = {token: changePasswordToken}; + throw err; + } + }; + + Self.sendTwoFactor = async(ctx, vnUser, myOptions) => { + if (vnUser.twoFactor === 'email') { + const $ = Self.app.models; + + const code = String(Math.floor(Math.random() * 999999)); + const maxTTL = ((60 * 1000) * 5); // 5 min + await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, { + userFk: vnUser.id, + code: code, + expires: Date.vnNow() + maxTTL + }, myOptions); + + const headers = ctx.req.headers; + const platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, ''); + const browser = headers['sec-ch-ua']?.replace(/['"=]+/g, ''); + const params = { + args: { + recipientId: vnUser.id, + recipient: vnUser.email, + code: code, + ip: ctx.req?.connection?.remoteAddress, + device: platform && browser ? platform + ', ' + browser : headers['user-agent'], + }, + req: {getLocale: ctx.req.getLocale}, + }; + + await Self.sendTemplate(params, 'auth-code', true); + } + }; +}; diff --git a/back/methods/vn-user/signIn.js b/back/methods/vn-user/signIn.js deleted file mode 100644 index e52d68df5..000000000 --- a/back/methods/vn-user/signIn.js +++ /dev/null @@ -1,81 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethod('signIn', { - description: 'Login a user with username/email and password', - accepts: [ - { - arg: 'user', - type: 'String', - description: 'The user name or email', - http: {source: 'form'}, - required: true - }, { - arg: 'password', - type: 'String', - description: 'The password' - } - ], - returns: { - type: 'object', - root: true - }, - http: { - path: `/signIn`, - verb: 'POST' - } - }); - - Self.signIn = async function(user, password) { - const models = Self.app.models; - const usesEmail = user.indexOf('@') !== -1; - let token; - - const userInfo = usesEmail - ? {email: user} - : {username: user}; - const instance = await Self.findOne({ - fields: ['username', 'password'], - where: userInfo - }); - - const where = usesEmail - ? {email: user} - : {name: user}; - const vnUser = await Self.findOne({ - fields: ['id', 'active', 'passExpired'], - where - }); - - const today = Date.vnNew(); - today.setHours(0, 0, 0, 0); - - const validCredentials = instance - && await instance.hasPassword(password); - - if (validCredentials) { - if (!vnUser.active) - throw new UserError('User disabled'); - - if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { - const changePasswordToken = await models.AccessToken.create({ - scopes: ['change-password'], - userId: vnUser.id - }); - const err = new UserError('Pass expired', 'passExpired'); - err.details = {token: changePasswordToken}; - throw err; - } - - try { - await models.Account.sync(instance.username, password); - } catch (err) { - console.warn(err); - } - } - - let loginInfo = Object.assign({password}, userInfo); - token = await Self.login(loginInfo, 'user'); - return {token: token.id, ttl: token.ttl}; - }; -}; diff --git a/back/methods/vn-user/specs/sign-in.spec.js b/back/methods/vn-user/specs/sign-in.spec.js new file mode 100644 index 000000000..f4cad88b9 --- /dev/null +++ b/back/methods/vn-user/specs/sign-in.spec.js @@ -0,0 +1,101 @@ +const {models} = require('vn-loopback/server/server'); + +describe('VnUser Sign-in()', () => { + const employeeId = 1; + const unauthCtx = { + req: { + headers: {}, + connection: { + remoteAddress: '127.0.0.1' + }, + getLocale: () => 'en' + }, + args: {} + }; + const {VnUser, AccessToken} = models; + describe('when credentials are correct', () => { + it('should return the token', async() => { + let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare'); + let accessToken = await AccessToken.findById(login.token); + let ctx = {req: {accessToken: accessToken}}; + + expect(login.token).toBeDefined(); + + await VnUser.logout(ctx.req.accessToken.id); + }); + + it('should return the token if the user doesnt exist but the client does', async() => { + let login = await VnUser.signIn(unauthCtx, 'PetterParker', 'nightmare'); + let accessToken = await AccessToken.findById(login.token); + let ctx = {req: {accessToken: accessToken}}; + + expect(login.token).toBeDefined(); + + await VnUser.logout(ctx.req.accessToken.id); + }); + }); + + describe('when credentials are incorrect', () => { + it('should throw a 401 error', async() => { + let error; + + try { + await VnUser.signIn(unauthCtx, 'IDontExist', 'TotallyWrongPassword'); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(401); + expect(error.code).toBe('LOGIN_FAILED'); + }); + }); + + describe('when two-factor auth is required', () => { + it('should throw a 403 error', async() => { + const employee = await VnUser.findById(employeeId); + const tx = await VnUser.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + await employee.updateAttribute('twoFactor', 'email', options); + + await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(403); + expect(error.code).toBe('REQUIRES_2FA'); + }); + }); + + describe('when passExpired', () => { + it('should throw a passExpired error', async() => { + const tx = await VnUser.beginTransaction({}); + const employee = await VnUser.findById(employeeId); + const yesterday = Date.vnNew(); + yesterday.setDate(yesterday.getDate() - 1); + + let error; + try { + const options = {transaction: tx}; + await employee.updateAttribute('passExpired', yesterday, options); + + await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toBe('Pass expired'); + }); + }); +}); diff --git a/back/methods/vn-user/specs/signIn.spec.js b/back/methods/vn-user/specs/signIn.spec.js deleted file mode 100644 index c3f4630c6..000000000 --- a/back/methods/vn-user/specs/signIn.spec.js +++ /dev/null @@ -1,41 +0,0 @@ -const {models} = require('vn-loopback/server/server'); - -describe('VnUser signIn()', () => { - describe('when credentials are correct', () => { - it('should return the token', async() => { - let login = await models.VnUser.signIn('salesAssistant', 'nightmare'); - let accessToken = await models.AccessToken.findById(login.token); - let ctx = {req: {accessToken: accessToken}}; - - expect(login.token).toBeDefined(); - - await models.VnUser.logout(ctx.req.accessToken.id); - }); - - it('should return the token if the user doesnt exist but the client does', async() => { - let login = await models.VnUser.signIn('PetterParker', 'nightmare'); - let accessToken = await models.AccessToken.findById(login.token); - let ctx = {req: {accessToken: accessToken}}; - - expect(login.token).toBeDefined(); - - await models.VnUser.logout(ctx.req.accessToken.id); - }); - }); - - describe('when credentials are incorrect', () => { - it('should throw a 401 error', async() => { - let error; - - try { - await models.VnUser.signIn('IDontExist', 'TotallyWrongPassword'); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error.statusCode).toBe(401); - expect(error.code).toBe('LOGIN_FAILED'); - }); - }); -}); diff --git a/back/methods/vn-user/specs/validate-auth.spec.js b/back/methods/vn-user/specs/validate-auth.spec.js new file mode 100644 index 000000000..8018bd3e1 --- /dev/null +++ b/back/methods/vn-user/specs/validate-auth.spec.js @@ -0,0 +1,52 @@ +const {models} = require('vn-loopback/server/server'); + +describe('VnUser validate-auth()', () => { + describe('validateAuth', () => { + it('should signin if data is correct', async() => { + await models.AuthCode.create({ + userFk: 9, + code: '555555', + expires: Date.vnNow() + (60 * 1000) + }); + const token = await models.VnUser.validateAuth('developer', 'nightmare', '555555'); + + expect(token.token).toBeDefined(); + }); + }); + + describe('validateCode', () => { + it('should throw an error for a non existent code', async() => { + let error; + try { + await models.VnUser.validateCode('developer', '123456'); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toEqual('Invalid or expired verification code'); + }); + + it('should throw an error when a code doesn`t match the login username', async() => { + let error; + let authCode; + try { + authCode = await models.AuthCode.create({ + userFk: 1, + code: '555555', + expires: Date.vnNow() + (60 * 1000) + }); + + await models.VnUser.validateCode('developer', '555555'); + } catch (e) { + authCode && await authCode.destroy(); + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toEqual('Authentication failed'); + }); + }); +}); diff --git a/back/methods/vn-user/validate-auth.js b/back/methods/vn-user/validate-auth.js new file mode 100644 index 000000000..beab43417 --- /dev/null +++ b/back/methods/vn-user/validate-auth.js @@ -0,0 +1,66 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('validateAuth', { + description: 'Login a user with username/email and password', + accepts: [ + { + arg: 'user', + type: 'String', + description: 'The user name or email', + required: true + }, + { + arg: 'password', + type: 'String', + description: 'The password' + }, + { + arg: 'code', + type: 'String', + description: 'The auth code' + } + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/validate-auth`, + verb: 'POST' + } + }); + + Self.validateAuth = async(username, password, code, options) => { + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const token = Self.validateLogin(username, password); + await Self.validateCode(username, code, myOptions); + return token; + }; + + Self.validateCode = async(username, code, myOptions) => { + const {AuthCode} = Self.app.models; + + const authCode = await AuthCode.findOne({ + where: { + code: code + } + }, myOptions); + + const expired = authCode && Date.vnNow() > authCode.expires; + if (!authCode || expired) + throw new UserError('Invalid or expired verification code'); + + const user = await Self.findById(authCode.userFk, { + fields: ['name', 'twoFactor'] + }, myOptions); + + if (user.name !== username) + throw new UserError('Authentication failed'); + + await authCode.destroy(myOptions); + }; +}; diff --git a/back/model-config.json b/back/model-config.json index d945f3250..0e37bf527 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -1,7 +1,4 @@ { - "AccountingType": { - "dataSource": "vn" - }, "AccessTokenConfig": { "dataSource": "vn", "options": { @@ -10,6 +7,12 @@ } } }, + "AccountingType": { + "dataSource": "vn" + }, + "AuthCode": { + "dataSource": "vn" + }, "Bank": { "dataSource": "vn" }, diff --git a/back/models/auth-code.json b/back/models/auth-code.json new file mode 100644 index 000000000..b6a89115f --- /dev/null +++ b/back/models/auth-code.json @@ -0,0 +1,31 @@ +{ + "name": "AuthCode", + "base": "VnModel", + "options": { + "mysql": { + "table": "salix.authCode" + } + }, + "properties": { + "userFk": { + "type": "number", + "required": true, + "id": true + }, + "code": { + "type": "string", + "required": true + }, + "expires": { + "type": "number", + "required": true + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "userFk" + } + } +} diff --git a/back/models/vn-user.js b/back/models/vn-user.js index b58395acc..a7ce12073 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -5,11 +5,12 @@ const {Email} = require('vn-print'); module.exports = function(Self) { vnModel(Self); - require('../methods/vn-user/signIn')(Self); + require('../methods/vn-user/sign-in')(Self); require('../methods/vn-user/acl')(Self); require('../methods/vn-user/recover-password')(Self); require('../methods/vn-user/validate-token')(Self); require('../methods/vn-user/privileges')(Self); + require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/renew-token')(Self); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); @@ -111,6 +112,18 @@ module.exports = function(Self) { return email.send(); }); + Self.validateLogin = async function(user, password) { + let loginInfo = Object.assign({password}, Self.userUses(user)); + token = await Self.login(loginInfo, 'user'); + return {token: token.id, ttl: token.ttl}; + }; + + Self.userUses = function(user) { + return user.indexOf('@') !== -1 + ? {email: user} + : {username: user}; + }; + const _setPassword = Self.prototype.setPassword; Self.prototype.setPassword = async function(newPassword, options, cb) { if (cb === undefined && typeof options === 'function') { @@ -143,8 +156,9 @@ module.exports = function(Self) { } }; - Self.sharedClass._methods.find(method => method.name == 'changePassword') - .accessScopes = ['change-password']; + Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = + Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls + .filter(acl => acl.property != 'changePassword'); // FIXME: https://redmine.verdnatura.es/issues/5761 // Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { diff --git a/back/models/vn-user.json b/back/models/vn-user.json index 61e42f77a..9131c9134 100644 --- a/back/models/vn-user.json +++ b/back/models/vn-user.json @@ -59,7 +59,10 @@ }, "passExpired": { "type": "date" - } + }, + "twoFactor": { + "type": "string" + } }, "relations": { "role": { @@ -111,6 +114,13 @@ "principalId": "$authenticated", "permission": "ALLOW" }, + { + "property": "validateAuth", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, { "property": "privileges", "accessType": "*", diff --git a/db/changes/232801/00-authCode.sql b/db/changes/232801/00-authCode.sql new file mode 100644 index 000000000..a256db43f --- /dev/null +++ b/db/changes/232801/00-authCode.sql @@ -0,0 +1,13 @@ +create table `salix`.`authCode` +( + userFk int UNSIGNED not null, + code int not null, + expires bigint not null, + constraint authCode_pk + primary key (userFk), + constraint authCode_unique + unique (code), + constraint authCode_user_id_fk + foreign key (userFk) references `account`.`user` (id) + on update cascade on delete cascade +); diff --git a/db/changes/232801/00-client_create.sql b/db/changes/232801/00-client_create.sql index 0728ba05e..d21094dad 100644 --- a/db/changes/232801/00-client_create.sql +++ b/db/changes/232801/00-client_create.sql @@ -1,11 +1,11 @@ -DROP PROCEDURE IF EXISTS vn.clientCreate; +DROP PROCEDURE IF EXISTS `vn`.`clientCreate`; DELIMITER $$ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`( vFirstname VARCHAR(50), - vSurnames VARCHAR(50), - vFi VARCHAR(9), - vAddress TEXT, + vSurnames VARCHAR(50), + vFi VARCHAR(9), + vAddress TEXT, vPostcode CHAR(5), vCity VARCHAR(25), vProvinceFk SMALLINT(5), @@ -61,7 +61,7 @@ BEGIN provinceFk = vProvinceFk, city = vCity, postcode = vPostcode, - socialName = CONCAT(vSurnames, ' ', vFirstname), + socialName = CONCAT(vSurnames, ' ', vFirstname), payMethodFk = vPayMethodFk, dueDay = vDueDay, credit = vDefaultCredit, @@ -75,14 +75,14 @@ BEGIN isTaxDataChecked = vIsTaxDataChecked, hasCoreVnl = vHasCoreVnl, isActive = TRUE; - + INSERT INTO mandate (clientFk, companyFk, mandateTypeFk) SELECT vUserFk, vCompanyFk, vMandateTypeFk WHERE NOT EXISTS ( - SELECT id - FROM mandate - WHERE clientFk = vUserFk - AND companyFk = vCompanyFk + SELECT id + FROM mandate + WHERE clientFk = vUserFk + AND companyFk = vCompanyFk AND mandateTypeFk = vMandateTypeFk ); END$$ diff --git a/db/changes/232801/00-client_create2.sql b/db/changes/232801/00-client_create2.sql index 8ba4e78e5..f2e660351 100644 --- a/db/changes/232801/00-client_create2.sql +++ b/db/changes/232801/00-client_create2.sql @@ -1,17 +1,17 @@ -ALTER TABLE vn.clientConfig ADD defaultPayMethodFk tinyint(3) unsigned NULL; -ALTER TABLE vn.clientConfig ADD defaultDueDay int unsigned NULL; -ALTER TABLE vn.clientConfig ADD defaultCredit decimal(10, 2) NULL; -ALTER TABLE vn.clientConfig ADD defaultIsTaxDataChecked tinyint(1) NULL; -ALTER TABLE vn.clientConfig ADD defaultHasCoreVnl boolean NULL; -ALTER TABLE vn.clientConfig ADD defaultMandateTypeFk smallint(5) NULL; -ALTER TABLE vn.clientConfig ADD CONSTRAINT clientNewConfigPayMethod_FK FOREIGN KEY (dafaultPayMethodFk) REFERENCES vn.payMethod(id); -ALTER TABLE vn.clientConfig ADD CONSTRAINT clientNewConfigMandateType_FK FOREIGN KEY (defaultMandateTypeFk) REFERENCES vn.mandateType(id); +ALTER TABLE `vn`.`clientConfig` ADD defaultPayMethodFk tinyint(3) unsigned NULL; +ALTER TABLE `vn`.`clientConfig` ADD defaultDueDay int unsigned NULL; +ALTER TABLE `vn`.`clientConfig` ADD defaultCredit decimal(10, 2) NULL; +ALTER TABLE `vn`.`clientConfig` ADD defaultIsTaxDataChecked tinyint(1) NULL; +ALTER TABLE `vn`.`clientConfig` ADD defaultHasCoreVnl boolean NULL; +ALTER TABLE `vn`.`clientConfig` ADD defaultMandateTypeFk smallint(5) NULL; +ALTER TABLE `vn`.`clientConfig` ADD CONSTRAINT clientNewConfigPayMethod_FK FOREIGN KEY (defaultPayMethodFk) REFERENCES vn.payMethod(id); +ALTER TABLE `vn`.`clientConfig` ADD CONSTRAINT clientNewConfigMandateType_FK FOREIGN KEY (defaultMandateTypeFk) REFERENCES vn.mandateType(id); -UPDATE vn.clientConfig +UPDATE `vn`.`clientConfig` SET defaultPayMethodFk = 4, defaultDueDay = 5, defaultCredit = 300.0, defaultIsTaxDataChecked = 1, defaultHasCoreVnl = 1, defaultMandateTypeFk = 2 - WHERE id = 1; \ No newline at end of file + WHERE id = 1; diff --git a/db/changes/232801/00-department.sql b/db/changes/232801/00-department.sql new file mode 100644 index 000000000..3dcb8501d --- /dev/null +++ b/db/changes/232801/00-department.sql @@ -0,0 +1,24 @@ +alter table `vn`.`department` + add `twoFactor` ENUM ('email') null comment 'Default user two-factor auth type'; + +drop trigger `vn`.`department_afterUpdate`; + +DELIMITER $$ +$$ +create definer = root@localhost trigger `vn`.`department_afterUpdate` + after update + on department + for each row +BEGIN + IF !(OLD.parentFk <=> NEW.parentFk) THEN + UPDATE vn.department_recalc SET isChanged = TRUE; + END IF; + + IF !(OLD.twoFactor <=> NEW.twoFactor) THEN + UPDATE account.user u + JOIN vn.workerDepartment wd ON wd.workerFk = u.id + SET u.twoFactor = NEW.twoFactor + WHERE wd.departmentFk = NEW.id; + END IF; +END;$$ +DELIMITER ; diff --git a/db/changes/232801/00-user.sql b/db/changes/232801/00-user.sql new file mode 100644 index 000000000..376b3dbb1 --- /dev/null +++ b/db/changes/232801/00-user.sql @@ -0,0 +1,5 @@ +alter table `account`.`user` + add `twoFactor` ENUM ('email') null comment 'Two-factor auth type'; + +DELETE FROM `salix`.`ACL` + WHERE model = 'VnUser' AND property = 'changePassword'; diff --git a/db/changes/233001/.gitkeep b/db/changes/233001/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 80929aebc..5b4b64437 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -77,7 +77,10 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, ` ORDER BY id; INSERT INTO `account`.`account`(`id`) - SELECT id FROM `account`.`user`; + SELECT `u`.`id` + FROM `account`.`user` `u` + JOIN `account`.`role` `r` ON `u`.`role` = `r`.`id` + WHERE `r`.`name` <> 'customer'; INSERT INTO `vn`.`educationLevel` (`id`, `name`) VALUES @@ -382,6 +385,12 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`) (1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), (1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)); +INSERT INTO `vn`.`mandateType`(`id`, `name`) + VALUES + (1, 'B2B'), + (2, 'CORE'), + (3, 'LCR'); + INSERT INTO `vn`.`clientConfig`(`id`, `riskTolerance`, `maxCreditRows`, `maxPriceIncreasingRatio`, `riskScope`, `defaultPayMethodFk`, `defaultDueDay`, `defaultCredit`, `defaultIsTaxDataChecked`, `defaultHasCoreVnl`, `defaultMandateTypeFk`) VALUES (1, 200, 10, 0.25, 2, 4, 5, 300.00, 1, 1, 2); @@ -825,12 +834,6 @@ INSERT INTO `vn`.`greuge`(`id`, `clientFk`, `description`, `amount`, `shipped`, (11, 1101, 'some heritage charges', -15.99, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 5, 1), (12, 1101, 'some miscellaneous charges', 58.00, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 6, 1); -INSERT INTO `vn`.`mandateType`(`id`, `name`) - VALUES - (1, 'B2B'), - (2, 'CORE'), - (3, 'LCR'); - INSERT INTO `vn`.`mandate`(`id`, `clientFk`, `companyFk`, `code`, `created`, `mandateTypeFk`) VALUES (1, 1102, 442, '1-1', util.VN_CURDATE(), 2); @@ -2849,8 +2852,8 @@ INSERT INTO `vn`.`profileType` (`id`, `name`) INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) VALUES - ('lilium', 'dev', 'http://localhost:9000/#/'), - ('salix', 'dev', 'http://localhost:5000/#!/'); + ('lilium', 'development', 'http://localhost:9000/#/'), + ('salix', 'development', 'http://localhost:5000/#!/'); INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`) VALUES diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index d4a8a316f..b10813488 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -894,6 +894,18 @@ export default { extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span', }, + department: { + firstDepartment: 'vn-worker-department-index vn-card > vn-treeview vn-treeview-childs vn-treeview-childs vn-treeview-childs a' + }, + departmentSummary: { + header: 'vn-worker-department-summary h5', + name: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(1) > section > span', + code: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(2) > section > span', + chat: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(3) > section > span', + bossDepartment: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(4) > section > span', + email: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(5) > section > span', + clientFk: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(6) > section > span', + }, workerBasicData: { name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]', surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]', @@ -901,6 +913,13 @@ export default { locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]', saveButton: 'vn-worker-basic-data button[type=submit]' }, + departmentBasicData: { + Name: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.name"]', + Code: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.code"]', + Chat: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.chat"]', + Email: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.notificationEmail"]', + saveButton: 'vn-worker-department-basic-data button[type=submit]' + }, workerNotes: { addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]', note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]', diff --git a/e2e/paths/01-salix/05_changePassword.spec.js b/e2e/paths/01-salix/05_changePassword.spec.js index 6e4cfb7f3..950f773dd 100644 --- a/e2e/paths/01-salix/05_changePassword.spec.js +++ b/e2e/paths/01-salix/05_changePassword.spec.js @@ -16,6 +16,7 @@ describe('ChangePassword path', async() => { await browser.close(); }); + const badPassword = 'badpass'; const oldPassword = 'nightmare'; const newPassword = 'newPass.1234'; describe('Bad login', async() => { @@ -37,13 +38,22 @@ describe('ChangePassword path', async() => { expect(message.text).toContain('Invalid current password'); // Bad attempt: password not meet requirements + message = await page.sendForm($.form, { + oldPassword: oldPassword, + newPassword: badPassword, + repeatPassword: badPassword + }); + + expect(message.text).toContain('Password does not meet requirements'); + + // Bad attempt: same password message = await page.sendForm($.form, { oldPassword: oldPassword, newPassword: oldPassword, repeatPassword: oldPassword }); - expect(message.text).toContain('Password does not meet requirements'); + expect(message.text).toContain('You can not use the same password'); // Correct attempt: change password message = await page.sendForm($.form, { diff --git a/e2e/paths/03-worker/01-department/01_summary.spec.js b/e2e/paths/03-worker/01-department/01_summary.spec.js new file mode 100644 index 000000000..e4bf8fc2d --- /dev/null +++ b/e2e/paths/03-worker/01-department/01_summary.spec.js @@ -0,0 +1,29 @@ +import selectors from '../../../helpers/selectors.js'; +import getBrowser from '../../../helpers/puppeteer'; + +describe('department summary path', () => { + let browser; + let page; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('hr', 'worker'); + await page.accessToSection('worker.department'); + await page.doSearch('INFORMATICA'); + await page.click(selectors.department.firstDepartment); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should reach the employee summary section and check all properties', async() => { + expect(await page.waitToGetProperty(selectors.departmentSummary.header, 'innerText')).toEqual('INFORMATICA'); + expect(await page.getProperty(selectors.departmentSummary.name, 'innerText')).toEqual('INFORMATICA'); + expect(await page.getProperty(selectors.departmentSummary.code, 'innerText')).toEqual('it'); + expect(await page.getProperty(selectors.departmentSummary.chat, 'innerText')).toEqual('informatica-cau'); + expect(await page.getProperty(selectors.departmentSummary.bossDepartment, 'innerText')).toEqual(''); + expect(await page.getProperty(selectors.departmentSummary.email, 'innerText')).toEqual('-'); + expect(await page.getProperty(selectors.departmentSummary.clientFk, 'innerText')).toEqual('-'); + }); +}); diff --git a/e2e/paths/03-worker/01-department/02-basicData.spec.js b/e2e/paths/03-worker/01-department/02-basicData.spec.js new file mode 100644 index 000000000..219d1426c --- /dev/null +++ b/e2e/paths/03-worker/01-department/02-basicData.spec.js @@ -0,0 +1,43 @@ +import getBrowser from '../../../helpers/puppeteer'; +import selectors from '../../../helpers/selectors.js'; + +const $ = { + form: 'vn-worker-department-basic-data form', +}; + +describe('department summary path', () => { + let browser; + let page; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('hr', 'worker'); + await page.accessToSection('worker.department'); + await page.doSearch('INFORMATICA'); + await page.click(selectors.department.firstDepartment); + }); + + beforeEach(async() => { + await page.accessToSection('worker.department.card.basicData'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it(`should edit the department basic data and confirm the department data was edited`, async() => { + const values = { + Name: 'Informatica', + Code: 'IT', + Chat: 'informatica-cau', + Email: 'it@verdnatura.es', + }; + + await page.fillForm($.form, values); + const formValues = await page.fetchForm($.form, Object.keys(values)); + const message = await page.sendForm($.form, values); + + expect(message.isSuccess).toBeTrue(); + expect(formValues).toEqual(values); + }); +}); diff --git a/front/core/services/auth.js b/front/core/services/auth.js index 92ff4b061..844a5145d 100644 --- a/front/core/services/auth.js +++ b/front/core/services/auth.js @@ -24,7 +24,7 @@ export default class Auth { initialize() { let criteria = { to: state => { - const outLayout = ['login', 'recover-password', 'reset-password', 'change-password']; + const outLayout = ['login', 'recover-password', 'reset-password', 'change-password', 'validate-email']; return !outLayout.some(ol => ol == state.name); } }; @@ -60,7 +60,25 @@ export default class Auth { }; const now = new Date(); - return this.$http.post('VnUsers/signIn', params) + return this.$http.post('VnUsers/sign-in', params).then( + json => this.onLoginOk(json, now, remember)); + } + + validateCode(user, password, code, remember) { + if (!user) { + let err = new UserError('Please enter your username'); + err.code = 'EmptyLogin'; + return this.$q.reject(err); + } + + let params = { + user: user, + password: password || undefined, + code: code + }; + + const now = new Date(); + return this.$http.post('VnUsers/validate-auth', params) .then(json => this.onLoginOk(json, now, remember)); } diff --git a/front/core/services/token.js b/front/core/services/token.js index 8f9f80e5c..c4b644a89 100644 --- a/front/core/services/token.js +++ b/front/core/services/token.js @@ -34,7 +34,6 @@ export default class Token { remember }); this.vnInterceptor.setToken(token); - try { if (remember) this.setStorage(localStorage, token, created, ttl); diff --git a/front/salix/components/change-password/index.html b/front/salix/components/change-password/index.html index 8d338d411..04f66976e 100644 --- a/front/salix/components/change-password/index.html +++ b/front/salix/components/change-password/index.html @@ -21,6 +21,14 @@ type="password" autocomplete="false"> + +