WIP: #6427 - SMS Recover Password #2037
|
@ -1,41 +1,16 @@
|
|||
const got = require('got');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
const isProduction = require('vn-loopback/server/boot/isProduction');
|
||||
const {models} = require('vn-loopback/server/server');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('send', {
|
||||
description: 'Sends SMS to a destination phone',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'destination',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
arg: 'message',
|
||||
type: 'string',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/send`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
Self.send = async(senderFk, destination, message, options) => {
|
||||
|
||||
const smsConfig = await models.SmsConfig.findOne();
|
||||
|
||||
Self.send = async(ctx, destination, message) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const smsConfig = await Self.app.models.SmsConfig.findOne();
|
||||
|
||||
if (destination.length == 9) {
|
||||
const spainPrefix = '0034';
|
||||
const [{prefix: spainPrefix}] = await Self.rawSql(
|
||||
'SELECT prefix FROM pbx.prefix WHERE country = ?', ['es'], options
|
||||
);
|
||||
if (destination.length == 9)
|
||||
destination = spainPrefix + destination;
|
||||
}
|
||||
|
||||
const params = {
|
||||
api_key: smsConfig.apiKey,
|
||||
|
@ -51,25 +26,26 @@ module.exports = Self => {
|
|||
if (!isProduction(false))
|
||||
response = {result: [{status: 'ok'}]};
|
||||
else {
|
||||
const jsonTest = {
|
||||
const body = {
|
||||
json: params
|
||||
};
|
||||
response = await got.post(smsConfig.uri, jsonTest).json();
|
||||
response = await got.post(smsConfig.uri, body).json();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (!options?.insert) return;
|
||||
|
||||
const [result] = response.result;
|
||||
const error = result.error_id;
|
||||
|
||||
if (senderFk) senderFk = senderFk.req.accessToken.userId;
|
||||
const newSms = {
|
||||
senderFk: userId,
|
||||
senderFk,
|
||||
destination: destination,
|
||||
message: message,
|
||||
status: error
|
||||
};
|
||||
|
||||
const sms = await Self.create(newSms);
|
||||
|
||||
if (error)
|
|
@ -3,8 +3,15 @@ const app = require('vn-loopback/server/server');
|
|||
describe('sms send()', () => {
|
||||
it('should not return status error', async() => {
|
||||
const ctx = {req: {accessToken: {userId: 1}}};
|
||||
const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body');
|
||||
const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body', {insert: true});
|
||||
|
||||
expect(result.status).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not insert', async() => {
|
||||
const ctx = {req: {accessToken: {userId: 1}}};
|
||||
const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body', {insert: false});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const isProduction = require('vn-loopback/server/boot/isProduction');
|
||||
const authCode = require('../../models/authCode');
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('recoverPasswordSMS', {
|
||||
description: 'Send SMS to the user',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'user',
|
||||
type: 'string',
|
||||
description: 'The recoveryPhone user\'s',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'verificationCode',
|
||||
type: 'string',
|
||||
description: 'Code tovalidate operation'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/recoverPasswordSMS`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.recoverPasswordSMS = async function(user, verificationCode, options) {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const usesEmail = user.indexOf('@') !== -1;
|
||||
const filter = usesEmail ? {email: user} : {name: user};
|
||||
const account = await models.VnUser.findOne({
|
||||
fields: ['id', 'name', 'recoveryPhone'],
|
||||
where: filter
|
||||
});
|
||||
if (!account && !verificationCode) return;
|
||||
user = account;
|
||||
|
||||
if (verificationCode) {
|
||||
if (!account)
|
||||
throw new UserError('Invalid or expired verification code');
|
||||
await Self.validateCode(user.name, verificationCode);
|
||||
|
||||
return {
|
||||
token: await user.accessTokens.create({})
|
||||
};
|
||||
}
|
||||
|
||||
const code = await authCode(user, myOptions);
|
||||
|
||||
if (!isProduction()) {
|
||||
try {
|
||||
await Self.app.models.Sms.send(null, +user.recoveryPhone, code, {insert: false});
|
||||
} catch (e) {
|
||||
throw new UserError(`We weren't able to send this SMS`);
|
||||
}
|
||||
}
|
||||
return {code: true};
|
||||
};
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
const authCode = require('../../models/authCode');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('signIn', {
|
||||
|
@ -65,18 +66,7 @@ module.exports = Self => {
|
|||
|
||||
Self.sendTwoFactor = async(ctx, vnUser, myOptions) => {
|
||||
if (vnUser.twoFactor === 'email') {
|
||||
const $ = Self.app.models;
|
||||
|
||||
const min = 100000;
|
||||
const max = 999999;
|
||||
const code = String(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||
const maxTTL = ((60 * 1000) * 5); // 5 min
|
||||
await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
|
||||
userFk: vnUser.id,
|
||||
code: code,
|
||||
expires: Date.vnNow() + maxTTL
|
||||
}, myOptions);
|
||||
|
||||
const code = await authCode(vnUser, myOptions);
|
||||
const headers = ctx.req.headers;
|
||||
const platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
|
||||
const browser = headers['sec-ch-ua']?.replace(/['"=]+/g, '');
|
||||
|
|
|
@ -20,6 +20,10 @@ module.exports = Self => {
|
|||
arg: 'email',
|
||||
type: 'string',
|
||||
description: 'The user email'
|
||||
}, {
|
||||
arg: 'recoveryPhone',
|
||||
type: 'string',
|
||||
description: 'The user email'
|
||||
}, {
|
||||
arg: 'lang',
|
||||
type: 'any',
|
||||
|
@ -36,8 +40,8 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.updateUser = async(ctx, id, name, nickname, email, lang, twoFactor) => {
|
||||
Self.updateUser = async(ctx, id, name, nickname, email, recoveryPhone, lang, twoFactor) => {
|
||||
await Self.userSecurity(ctx, id);
|
||||
await Self.upsertWithWhere({id}, {name, nickname, email, lang, twoFactor});
|
||||
await Self.upsertWithWhere({id}, {name, nickname, email, recoveryPhone, lang, twoFactor});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const maxTTL = ((60 * 1000) * 5); // 5 min
|
||||
|
||||
module.exports = authCode = async(vnUser, options) => {
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const min = 100000;
|
||||
const max = 999999;
|
||||
const code = String(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||
|
||||
await models.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
|
||||
userFk: vnUser.id,
|
||||
code,
|
||||
expires: Date.vnNow() + maxTTL
|
||||
}, myOptions);
|
||||
return code;
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "smsConfig"
|
||||
"table": "vn.smsConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
|
@ -1,4 +1,3 @@
|
|||
module.exports = Self => {
|
||||
// Methods
|
||||
require('../methods/sms/send')(Self);
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "sms"
|
||||
"table": "vn.sms"
|
||||
}
|
||||
},
|
||||
"properties": {
|
|
@ -1,7 +1,6 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('VnUser recoverPassword()', () => {
|
||||
describe('VnUser recoverPassword', () => {
|
||||
const userId = 1107;
|
||||
|
||||
const activeCtx = {
|
||||
|
@ -19,6 +18,7 @@ describe('VnUser recoverPassword()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('By email', () => {
|
||||
it('should send email with token', async() => {
|
||||
const userId = 1107;
|
||||
const user = await models.VnUser.findById(userId);
|
||||
|
@ -29,4 +29,18 @@ describe('VnUser recoverPassword()', () => {
|
|||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('By SMS()', () => {
|
||||
it('should send sms with token', async() => {
|
||||
const userId = 1107;
|
||||
const user = await models.VnUser.findById(userId);
|
||||
|
||||
await models.VnUser.recoverPasswordSMS(user.email);
|
||||
|
||||
const result = await models.AuthCode.findOne({where: {userId: userId}});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('loopback model VnUser', () => {
|
|||
await models.VnUser.userSecurity(ctx, employeeId);
|
||||
});
|
||||
|
||||
it('should throw an error if you have medium privileges and the users email is verified', async() => {
|
||||
it('should throw an error when update emailVerified field if you have medium privileges and the users email is verified', async() => {
|
||||
const tx = await models.VnUser.beginTransaction({});
|
||||
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||
try {
|
||||
|
@ -50,5 +50,32 @@ describe('loopback model VnUser', () => {
|
|||
expect(error).toEqual(new ForbiddenError());
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error when update recoveryPhone if you have medium privileges and the users email is verified', async() => {
|
||||
const tx = await models.VnUser.beginTransaction({});
|
||||
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const userToUpdate = await models.VnUser.findById(1, null, options);
|
||||
userToUpdate.updateAttribute('recoveryPhone', 123456789, options);
|
||||
|
||||
await models.VnUser.userSecurity(ctx, employeeId, options);
|
||||
await tx.rollback();
|
||||
} catch (error) {
|
||||
await tx.rollback();
|
||||
|
||||
expect(error).toEqual(new ForbiddenError());
|
||||
}
|
||||
});
|
||||
|
||||
it('should update recoveryPhone if you are the same user', async() => {
|
||||
const ctx = {options: {accessToken: {userId: employeeId}}};
|
||||
const newRecoveryPhone = 123456789;
|
||||
|
||||
const userToUpdate = await models.VnUser.findById(1, null);
|
||||
userToUpdate.updateAttribute('recoveryPhone', newRecoveryPhone);
|
||||
|
||||
await models.VnUser.userSecurity(ctx, employeeId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports = function(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/recover-passwordSMS')(Self);
|
||||
require('../methods/vn-user/privileges')(Self);
|
||||
require('../methods/vn-user/validate-auth')(Self);
|
||||
require('../methods/vn-user/renew-token')(Self);
|
||||
|
|
|
@ -61,6 +61,9 @@
|
|||
},
|
||||
"twoFactor": {
|
||||
"type": "string"
|
||||
},
|
||||
"recoveryPhone": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
@ -106,6 +109,13 @@
|
|||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "recoverPasswordSMS",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "validateAuth",
|
||||
"accessType": "EXECUTE",
|
||||
|
@ -166,6 +176,7 @@
|
|||
"realm",
|
||||
"email",
|
||||
"emailVerified",
|
||||
"recoveryPhone",
|
||||
"twoFactor"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -311,4 +311,10 @@ INSERT INTO mysql.roles_mapping (`User`, `Host`, `Role`, `Admin_option`)
|
|||
SELECT SUBSTR(`User`, @prefixLen + 1), `Host`, `Role`, `Admin_option`
|
||||
FROM mysql.roles_mapping
|
||||
WHERE `User` LIKE @prefixedLike AND `Host` = @genRoleHost;
|
||||
|
||||
-- Actualiza los valores de la nueva columna con los valores correspondientes de la tabla userInfo
|
||||
UPDATE `account`.`user` as `user`
|
||||
JOIN vn.client `client` ON `user`.id = `client`.id
|
||||
SET `user`.recoveryPhone = `client`.phone;
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
|
|
|
@ -3204,6 +3204,10 @@ INSERT INTO `vn`.`cmr` (id,truckPlate,observations,senderInstruccions,paymentIns
|
|||
(2,'123456N','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet',69,3,4,2,'Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet'),
|
||||
(3,'123456B','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet',567,5,6,69,'Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet');
|
||||
|
||||
UPDATE `vn`.`client`
|
||||
SET phone= 432978106
|
||||
WHERE id=9;
|
||||
|
||||
UPDATE vn.department
|
||||
SET workerFk = null;
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#6427 -- Place your SQL code here
|
||||
ALTER TABLE account.`user` ADD recoveryPhone varchar(20) NULL;
|
||||
|
||||
-- ALTER TABLE vn.`sms` MODIFY COLUMN senderFk int(10) unsigned NULL;
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
-- Actualiza los valores de la nueva columna con los valores correspondientes de la tabla userInfo
|
||||
UPDATE `account`.`user` as `user`
|
||||
JOIN vn.client `client` ON `user`.id = `client`.id
|
||||
SET `user`.recoveryPhone = `client`.phone;
|
|
@ -33,7 +33,10 @@ export default {
|
|||
recoverPassword: {
|
||||
recoverPasswordButton: 'vn-login a[ui-sref="recover-password"]',
|
||||
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.user"]',
|
||||
code: 'vn-recover-password vn-textfield[ng-model="$ctrl.verificationCode"]',
|
||||
sendEmailButton: 'vn-recover-password vn-submit',
|
||||
smsOption: 'vn-recover-password vn-radio[val="sms"]',
|
||||
emailOption: 'vn-recover-password vn-radio[val="email"]',
|
||||
},
|
||||
accountIndex: {
|
||||
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
||||
|
|
|
@ -8,6 +8,10 @@ describe('RecoverPassword path', async() => {
|
|||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
});
|
||||
|
||||
beforeEach(async() => {
|
||||
await page.waitForState('login');
|
||||
|
||||
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||
await page.waitForState('recover-password');
|
||||
|
@ -17,36 +21,78 @@ describe('RecoverPassword path', async() => {
|
|||
await browser.close();
|
||||
});
|
||||
|
||||
it('should not throw error if not exist user', async() => {
|
||||
it('should not throw error if not exist user when select email option', async() => {
|
||||
await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com');
|
||||
await page.waitToClick(selectors.recoverPassword.emailOption);
|
||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||
const httpDataResponse = await page.waitForResponse(response => {
|
||||
return response.status() === 204 && response.url().includes(`VnUsers/recoverPassword`);
|
||||
});
|
||||
const code = await httpDataResponse.ok();
|
||||
|
||||
expect(code).toBeTrue();
|
||||
const message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain('Notification sent!');
|
||||
});
|
||||
|
||||
it('should send email using email', async() => {
|
||||
await page.waitForState('login');
|
||||
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||
|
||||
await page.write(selectors.recoverPassword.email, 'BruceWayne@mydomain.com');
|
||||
await page.waitToClick(selectors.recoverPassword.emailOption);
|
||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||
const message = await page.waitForSnackbar();
|
||||
await page.waitForState('login');
|
||||
|
||||
expect(message.text).toContain('Notification sent!');
|
||||
});
|
||||
|
||||
it('should send email using username', async() => {
|
||||
await page.waitForState('login');
|
||||
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||
|
||||
await page.write(selectors.recoverPassword.email, 'BruceWayne');
|
||||
await page.waitToClick(selectors.recoverPassword.emailOption);
|
||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||
const message = await page.waitForSnackbar();
|
||||
await page.waitForState('login');
|
||||
|
||||
expect(message.text).toContain('Notification sent!');
|
||||
});
|
||||
|
||||
it('should send sms using username', async() => {
|
||||
await page.setRequestInterception(true);
|
||||
|
||||
await page.write(selectors.recoverPassword.email, 'BruceWayne');
|
||||
await page.waitToClick(selectors.recoverPassword.smsOption);
|
||||
page.on('request', request => {
|
||||
if (request.url().includes('recoverPasswordSMS')) {
|
||||
const body = JSON.parse(request.postData());
|
||||
const isVerificationCode = Object.keys(body).includes('verificationCode');
|
||||
if (!isVerificationCode) {
|
||||
request.respond({
|
||||
content: 'application/json',
|
||||
headers: {'Access-Control-Allow-Origin': '*'},
|
||||
body: JSON.stringify({code: '123456'})
|
||||
});
|
||||
} else {
|
||||
request.respond({
|
||||
content: 'application/json',
|
||||
headers: {'Access-Control-Allow-Origin': '*'},
|
||||
body: JSON.stringify({token: {
|
||||
'id': 'A7al0KNofU7RFL5XPNubKsVjOAj80eoydXhm9i6rF4gj5kom6nEx4BG2bubzLocm',
|
||||
'ttl': 1209600,
|
||||
'created': '2024-05-30T10:43:36.014Z',
|
||||
'userId': 9
|
||||
}})
|
||||
});
|
||||
}
|
||||
} else
|
||||
request.continue();
|
||||
});
|
||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||
const httpDataResponse = await page.waitForResponse(response => {
|
||||
return response.status() === 200 && response.url().includes(`VnUsers/recoverPasswordSMS`);
|
||||
});
|
||||
const {code} = await httpDataResponse.json();
|
||||
await page.write(selectors.recoverPassword.code, code);
|
||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||
await page.waitForState('reset-password');
|
||||
|
||||
expect(await page.getState()).toContain('reset-password');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,16 +1,49 @@
|
|||
<h5 class="vn-mb-md vn-mt-lg" translate>Recover password</h5>
|
||||
<vn-textfield
|
||||
disabled="$ctrl.code"
|
||||
label="User or recovery email"
|
||||
ng-model="$ctrl.user"
|
||||
vn-focus>
|
||||
vn-focus
|
||||
>
|
||||
</vn-textfield>
|
||||
<div
|
||||
class="text-secondary"
|
||||
translate>
|
||||
<vn-textfield
|
||||
ng-if="$ctrl.code"
|
||||
label="Verification code"
|
||||
ng-model="$ctrl.verificationCode"
|
||||
vn-name="verificationCode"
|
||||
autocomplete="false"
|
||||
class="vn-mt-md">
|
||||
</vn-textfield>
|
||||
<vn-one>
|
||||
<vn-vertical class="vn-mb-sm">
|
||||
<vn-radio
|
||||
disabled="$ctrl.code"
|
||||
label="Teléfono móvil"
|
||||
val="sms"
|
||||
ng-model="$ctrl.method">
|
||||
</vn-radio>
|
||||
<vn-radio
|
||||
disabled="$ctrl.code"
|
||||
label="E-mail"
|
||||
val="email"
|
||||
ng-model="$ctrl.method">
|
||||
</vn-radio>
|
||||
</vn-vertical>
|
||||
</vn-one>
|
||||
<div class="text-secondary" ng-if="$ctrl.method && $ctrl.user">
|
||||
<span ng-if="$ctrl.method ==='sms'" translate>
|
||||
We will sent you a sms to recover your password
|
||||
</span>
|
||||
<span ng-if="$ctrl.method ==='email'" translate>
|
||||
We will sent you an email to recover your password
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<vn-submit label="Recover password" ng-click="$ctrl.submit()"></vn-submit>
|
||||
<vn-submit
|
||||
disabled="!$ctrl.user || !$ctrl.method || ($ctrl.code&&!$ctrl.verificationCode)"
|
||||
label="Recover password"
|
||||
ng-click="$ctrl.submit()"
|
||||
></vn-submit>
|
||||
<div class="spinner-wrapper">
|
||||
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import UserError from '../../../core/lib/user-error';
|
||||
import ngModule from '../../module';
|
||||
|
||||
export default class Controller {
|
||||
constructor($scope, $element, $http, vnApp, $translate, $state) {
|
||||
constructor($scope, $element, $http, vnApp, $translate, $state, $location) {
|
||||
Object.assign(this, {
|
||||
$scope,
|
||||
$element,
|
||||
$http,
|
||||
vnApp,
|
||||
$translate,
|
||||
$state
|
||||
$state,
|
||||
$location
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,19 +18,49 @@ export default class Controller {
|
|||
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
|
||||
this.$state.go('login');
|
||||
}
|
||||
goToChangePassword({token}) {
|
||||
if (!token)
|
||||
this.vnApp.showError(this.$translate.instant('Invalid login'));
|
||||
else
|
||||
this.$location.path('/reset-password').search('access_token', token.id);
|
||||
}
|
||||
handleCode(code) {
|
||||
this.code = true;
|
||||
this.$state.params.verificationCode = code;
|
||||
}
|
||||
methodsAvailables() {
|
||||
return {
|
||||
'email': {
|
||||
url: 'VnUsers/recoverPassword',
|
||||
data: {user: this.user},
|
||||
cb: data => {
|
||||
data === '' && this.goToLogin();
|
||||
}
|
||||
|
||||
},
|
||||
'sms': {
|
||||
url: 'VnUsers/recoverPasswordSMS',
|
||||
data: {user: this.user, verificationCode: this.verificationCode},
|
||||
cb: data => {
|
||||
if (this.method && this.code) {
|
||||
data.token && this.goToChangePassword(data);
|
||||
if (!data.token) throw new UserError(`Credentials not valid`);
|
||||
} else
|
||||
this.handleCode(data.code);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
submit() {
|
||||
const params = {
|
||||
user: this.user
|
||||
};
|
||||
|
||||
this.$http.post('VnUsers/recoverPassword', params)
|
||||
.then(() => {
|
||||
this.goToLogin();
|
||||
});
|
||||
if (!this.user || (this.sms) || (this.code && !this.code))
|
||||
throw new UserError(`Credentials not valid`);
|
||||
const method = this.methodsAvailables()[this.method];
|
||||
this.$http.post(method.url, method.data)
|
||||
.then(({data}) => method.cb(data));
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state'];
|
||||
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location'];
|
||||
|
||||
ngModule.vnComponent('vnRecoverPassword', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
Recover password: Recuperar contraseña
|
||||
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
|
||||
We will sent you a sms to recover your password: Te enviaremos un sms para restablecer tu contraseña
|
||||
Notification sent!: ¡Notificación enviada!
|
||||
User or recovery email: Usuario o correo de recuperación
|
||||
User or recovery email: Usuario
|
||||
User's phone: Móvil del usuario
|
||||
User's id: Id del usuario
|
||||
Credentials not valid: Credenciales no válidas
|
||||
E-mail: Correo electrónico
|
||||
|
|
|
@ -28,7 +28,7 @@ export default class Controller {
|
|||
throw new UserError(`Passwords don't match`);
|
||||
|
||||
const headers = {
|
||||
Authorization: this.$location.$$search.access_token
|
||||
Authorization: this.$location.$$search.access_token ?? this.$state.params.access_token
|
||||
};
|
||||
|
||||
const newPassword = this.newPassword;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Reset password: Restrablecer contraseña
|
||||
Reset password: Restablecer contraseña
|
||||
New password: Nueva contraseña
|
||||
Repeat password: Repetir contraseña
|
||||
Password changed!: ¡Contraseña cambiada!
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"InvoiceIn is already booked": "InvoiceIn is already booked",
|
||||
"This workCenter is already assigned to this agency": "This workCenter is already assigned to this agency",
|
||||
"You can only have one PDA": "You can only have one PDA",
|
||||
"Credentials not valid": "Credentials not valid",
|
||||
"Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member",
|
||||
"The invoices have been created but the PDFs could not be generated": "The invoices have been created but the PDFs could not be generated",
|
||||
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
|
||||
|
|
|
@ -397,5 +397,6 @@
|
|||
"Incorrect delivery order alert on route": "Alerta de orden de entrega incorrecta en ruta: {{ route }} zona: {{ zone }}",
|
||||
"Ticket has been delivered out of order": "El ticket {{ticket}} {{{fullUrl}}} no ha sido entregado en su orden.",
|
||||
"Price cannot be blank": "El precio no puede estar en blanco",
|
||||
"clonedFromTicketWeekly": ", que es una linea clonada del ticket {{ticketWeekly}}"
|
||||
"clonedFromTicketWeekly": ", que es una linea clonada del ticket {{ticketWeekly}}",
|
||||
"Credentials not valid": "Credenciales no válidas"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"name": "Account",
|
||||
"base": "VnModel",
|
||||
"mixins": {
|
||||
"Loggable": true
|
||||
},
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "account.account"
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<mg-ajax path="VnUsers/{{patch.params.id}}/update-user" options="vnPatch"></mg-ajax>
|
||||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.user"
|
||||
form="form"
|
||||
save="patch">
|
||||
</vn-watcher>
|
||||
<form
|
||||
name="form"
|
||||
ng-submit="$ctrl.onSubmit()"
|
||||
class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-vertical>
|
||||
<vn-textfield
|
||||
label="User"
|
||||
ng-model="$ctrl.user.name"
|
||||
rule="VnUser"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Nickname"
|
||||
ng-model="$ctrl.user.nickname"
|
||||
rule="VnUser">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Personal email"
|
||||
ng-model="$ctrl.user.email"
|
||||
rule="VnUser">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Recovery phone"
|
||||
ng-model="$ctrl.user.recoveryPhone"
|
||||
disabled="$root.user.id !== $ctrl.user.id">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
label="Language"
|
||||
ng-model="$ctrl.user.lang"
|
||||
url="Languages"
|
||||
value-field="code"
|
||||
rule="VnUser">
|
||||
</vn-autocomplete>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit
|
||||
disabled="!watcher.dataChanged()"
|
||||
label="Save">
|
||||
</vn-submit>
|
||||
<vn-button
|
||||
class="cancel"
|
||||
label="Undo changes"
|
||||
disabled="!watcher.dataChanged()"
|
||||
ng-click="watcher.loadOriginalData()">
|
||||
</vn-button>
|
||||
</vn-button-bar>
|
||||
</form>
|
|
@ -0,0 +1,3 @@
|
|||
Email verified successfully!: Correo verificado correctamente!
|
||||
Recovery phone: Teléfono de recuperación de cuenta
|
||||
|
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
|||
|
||||
Self.sendSms = async(ctx, id, destination, message) => {
|
||||
const models = Self.app.models;
|
||||
const sms = await models.Sms.send(ctx, destination, message);
|
||||
const sms = await models.Sms.send(ctx, destination, message, {insert: true});
|
||||
|
||||
await models.ClientSms.create({
|
||||
clientFk: id,
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports = Self => {
|
|||
|
||||
const allSms = [];
|
||||
for (let client of targetClients) {
|
||||
let sms = await Self.app.models.Sms.send(ctx, client, message);
|
||||
let sms = await Self.app.models.Sms.send(ctx, client, message, {insert: true});
|
||||
allSms.push(sms);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ module.exports = Self => {
|
|||
CALL vn.sale_recalcComponent(null);
|
||||
DROP TEMPORARY TABLE tmp.recalculateSales;`;
|
||||
|
||||
const recalculation = await Self.rawSql(query, [salesIds], myOptions);
|
||||
const recalculation = await Self.rawSql(query, salesIds, myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
|||
|
||||
Self.sendSms = async(ctx, id, destination, message) => {
|
||||
const models = Self.app.models;
|
||||
const sms = await models.Sms.send(ctx, destination, message);
|
||||
const sms = await models.Sms.send(ctx, destination, message, {insert: true});
|
||||
const {clientFk} = await models.Ticket.findById(id);
|
||||
await models.ClientSms.create({
|
||||
clientFk,
|
||||
|
|
Loading…
Reference in New Issue
Renombrar parametro
insert
alog
con valor por defectotrue