4077-login_recover-password & account_verifyEmail #1063
|
@ -0,0 +1,30 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('recoverPassword', {
|
||||||
|
description: 'Send email to the user',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'email',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The email of user',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/recoverPassword`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.recoverPassword = async function(email) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await models.user.resetPassword({email, emailTemplate: 'recover-password'});
|
||||||
alexm marked this conversation as resolved
Outdated
|
|||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'EMAIL_NOT_FOUND')
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('account changePassword()', () => {
|
describe('account setPassword()', () => {
|
||||||
it('should throw an error when password does not meet requirements', async() => {
|
it('should throw an error when password does not meet requirements', async() => {
|
||||||
let req = app.models.Account.setPassword(1, 'insecurePass');
|
let req = app.models.Account.setPassword(1, 'insecurePass');
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
/* eslint max-len: ["error", { "code": 150 }]*/
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
const {Email} = require('vn-print');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/account/login')(Self);
|
require('../methods/account/login')(Self);
|
||||||
|
@ -6,6 +9,7 @@ module.exports = Self => {
|
||||||
require('../methods/account/acl')(Self);
|
require('../methods/account/acl')(Self);
|
||||||
require('../methods/account/change-password')(Self);
|
require('../methods/account/change-password')(Self);
|
||||||
require('../methods/account/set-password')(Self);
|
require('../methods/account/set-password')(Self);
|
||||||
|
require('../methods/account/recover-password')(Self);
|
||||||
require('../methods/account/validate-token')(Self);
|
require('../methods/account/validate-token')(Self);
|
||||||
require('../methods/account/privileges')(Self);
|
require('../methods/account/privileges')(Self);
|
||||||
|
|
||||||
|
@ -27,17 +31,62 @@ module.exports = Self => {
|
||||||
ctx.data.password = md5(ctx.data.password);
|
ctx.data.password = md5(ctx.data.password);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
||||||
|
if (!ctx.args || !ctx.args.data.email) return;
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const httpCtx = {req: loopBackContext.active};
|
||||||
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
const url = origin.split(':');
|
||||||
|
|
||||||
|
const userId = ctx.instance.id;
|
||||||
|
const user = await models.user.findById(userId);
|
||||||
|
|
||||||
|
class Mailer {
|
||||||
|
async send(verifyOptions, cb) {
|
||||||
alexm marked this conversation as resolved
Outdated
juan
commented
Plantilla ejs Plantilla *ejs*
|
|||||||
|
const params = {
|
||||||
|
url: verifyOptions.verifyHref,
|
||||||
|
recipient: verifyOptions.to,
|
||||||
|
lang: ctx.req.getLocale()
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = new Email('email-verify', params);
|
||||||
|
email.send();
|
||||||
|
|
||||||
alexm marked this conversation as resolved
Outdated
juan
commented
Cridar al métode Cridar al métode `User.verify()` com ací:
* https://gitea.verdnatura.es/juan/hedera-web/src/branch/master/back/common/models/user.js#L22
* https://loopback.io/doc/en/lb2/Registering-users.html
|
|||||||
|
cb(null, verifyOptions.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
type: 'email',
|
||||||
|
to: instance.email,
|
||||||
|
from: {},
|
||||||
|
redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`,
|
||||||
|
template: false,
|
||||||
|
mailer: new Mailer,
|
||||||
|
host: url[1].split('/')[2],
|
||||||
|
port: url[2],
|
||||||
|
protocol: url[0],
|
||||||
|
user: Self
|
||||||
|
};
|
||||||
|
|
||||||
|
await user.verify(options);
|
||||||
|
});
|
||||||
|
|
||||||
Self.remoteMethod('getCurrentUserData', {
|
Self.remoteMethod('getCurrentUserData', {
|
||||||
description: 'Gets the current user data',
|
description: 'Gets the current user data',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
arg: 'ctx',
|
arg: 'ctx',
|
||||||
type: 'Object',
|
type: 'object',
|
||||||
http: {source: 'context'}
|
http: {source: 'context'}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'Object',
|
type: 'object',
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -58,7 +107,7 @@ module.exports = Self => {
|
||||||
*
|
*
|
||||||
* @param {Integer} userId The user id
|
* @param {Integer} userId The user id
|
||||||
* @param {String} name The role name
|
* @param {String} name The role name
|
||||||
* @param {Object} options Options
|
* @param {object} options Options
|
||||||
* @return {Boolean} %true if user has the role, %false otherwise
|
* @return {Boolean} %true if user has the role, %false otherwise
|
||||||
*/
|
*/
|
||||||
Self.hasRole = async function(userId, name, options) {
|
Self.hasRole = async function(userId, name, options) {
|
||||||
|
@ -70,8 +119,8 @@ module.exports = Self => {
|
||||||
* Get all user roles.
|
* Get all user roles.
|
||||||
*
|
*
|
||||||
* @param {Integer} userId The user id
|
* @param {Integer} userId The user id
|
||||||
* @param {Object} options Options
|
* @param {object} options Options
|
||||||
* @return {Object} User role list
|
* @return {object} User role list
|
||||||
*/
|
*/
|
||||||
Self.getRoles = async(userId, options) => {
|
Self.getRoles = async(userId, options) => {
|
||||||
let result = await Self.rawSql(
|
let result = await Self.rawSql(
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
@ -88,6 +91,13 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "recoverPassword",
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "logout",
|
"property": "logout",
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
describe('loopback model Account', () => {
|
describe('loopback model Account', () => {
|
||||||
it('should return true if the user has the given role', async() => {
|
it('should return true if the user has the given role', async() => {
|
||||||
let result = await app.models.Account.hasRole(1, 'employee');
|
let result = await models.Account.hasRole(1, 'employee');
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if the user doesnt have the given role', async() => {
|
it('should return false if the user doesnt have the given role', async() => {
|
||||||
let result = await app.models.Account.hasRole(1, 'administrator');
|
let result = await models.Account.hasRole(1, 'administrator');
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('account recoverPassword()', () => {
|
||||||
|
const userId = 1107;
|
||||||
|
|
||||||
|
const activeCtx = {
|
||||||
|
accessToken: {userId: userId},
|
||||||
|
http: {
|
||||||
|
req: {
|
||||||
|
headers: {origin: 'http://localhost'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: activeCtx
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send email with token', async() => {
|
||||||
|
const userId = 1107;
|
||||||
|
const user = await models.Account.findById(userId);
|
||||||
|
|
||||||
|
await models.Account.recoverPassword(user.email);
|
||||||
|
|
||||||
|
const result = await models.AccessToken.findOne({where: {userId: userId}});
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
const {Email} = require('vn-print');
|
||||||
|
|
||||||
|
module.exports = function(Self) {
|
||||||
|
Self.on('resetPasswordRequest', async function(info) {
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const httpCtx = {req: loopBackContext.active};
|
||||||
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
|
||||||
|
const user = await Self.app.models.Account.findById(info.user.id);
|
||||||
|
const params = {
|
||||||
|
recipient: info.email,
|
||||||
|
lang: user.lang,
|
||||||
|
url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = Object.assign({}, info.options);
|
||||||
|
for (const param in options)
|
||||||
|
params[param] = options[param];
|
||||||
|
|
||||||
|
const email = new Email(options.emailTemplate, params);
|
||||||
|
|
||||||
|
return email.send();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
DELETE FROM `salix`.`ACL`
|
||||||
|
WHERE model = 'UserPassword';
|
|
@ -29,6 +29,11 @@ export default {
|
||||||
firstModulePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="push_pin"]',
|
firstModulePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="push_pin"]',
|
||||||
firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]'
|
firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]'
|
||||||
},
|
},
|
||||||
|
recoverPassword: {
|
||||||
|
recoverPasswordButton: 'vn-login a[ui-sref="recoverPassword"]',
|
||||||
|
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.email"]',
|
||||||
|
sendEmailButton: 'vn-recover-password vn-submit',
|
||||||
|
},
|
||||||
accountIndex: {
|
accountIndex: {
|
||||||
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
||||||
newName: 'vn-user-create vn-textfield[ng-model="$ctrl.user.name"]',
|
newName: 'vn-user-create vn-textfield[ng-model="$ctrl.user.name"]',
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import selectors from '../../helpers/selectors';
|
||||||
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('Login path', async() => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||||
|
await page.waitForState('recoverPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw error if not exist user', async() => {
|
||||||
|
await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
|
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toContain('Notification sent!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send 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.sendEmailButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
await page.waitForState('login');
|
||||||
|
|
||||||
|
expect(message.text).toContain('Notification sent!');
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,80 +5,78 @@ describe('Ticket Future path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async() => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
await page.loginAndModule('employee', 'ticket');
|
await page.loginAndModule('employee', 'ticket');
|
||||||
await page.accessToSection('ticket.future');
|
await page.accessToSection('ticket.future');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async() => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const tomorrow = new Date(now.getDate() + 1);
|
const tomorrow = new Date(now.getDate() + 1);
|
||||||
const ticket = {
|
|
||||||
originDated: now,
|
|
||||||
futureDated: now,
|
|
||||||
linesMax: '9999',
|
|
||||||
litersMax: '9999',
|
|
||||||
warehouseFk: 'Warehouse One'
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should show errors snackbar because of the required data', async () => {
|
it('should show errors snackbar because of the required data', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.warehouseFk);
|
await page.clearInput(selectors.ticketFuture.warehouseFk);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
let message = await page.waitForSnackbar();
|
let message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('warehouseFk is a required argument');
|
expect(message.text).toContain('warehouseFk is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.litersMax);
|
await page.clearInput(selectors.ticketFuture.litersMax);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('litersMax is a required argument');
|
expect(message.text).toContain('litersMax is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.linesMax);
|
await page.clearInput(selectors.ticketFuture.linesMax);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('linesMax is a required argument');
|
expect(message.text).toContain('linesMax is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.futureDated);
|
await page.clearInput(selectors.ticketFuture.futureDated);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('futureDated is a required argument');
|
expect(message.text).toContain('futureDated is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.originDated);
|
await page.clearInput(selectors.ticketFuture.originDated);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('originDated is a required argument');
|
expect(message.text).toContain('originDated is a required argument');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the required data', async () => {
|
it('should search with the required data', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin shipped today', async () => {
|
it('should search with the origin shipped today', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.pickDate(selectors.ticketFuture.shipped, now);
|
await page.pickDate(selectors.ticketFuture.shipped, now);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin shipped tomorrow', async () => {
|
it('should search with the origin shipped tomorrow', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.pickDate(selectors.ticketFuture.shipped, tomorrow);
|
await page.pickDate(selectors.ticketFuture.shipped, tomorrow);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination shipped today', async () => {
|
it('should search with the destination shipped today', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
await page.pickDate(selectors.ticketFuture.tfShipped, now);
|
await page.pickDate(selectors.ticketFuture.tfShipped, now);
|
||||||
|
@ -86,14 +84,14 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination shipped tomorrow', async () => {
|
it('should search with the destination shipped tomorrow', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow);
|
await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin IPT', async () => {
|
it('should search with the origin IPT', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -108,7 +106,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination IPT', async () => {
|
it('should search with the destination IPT', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -123,7 +121,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin grouped state', async () => {
|
it('should search with the origin grouped state', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -138,7 +136,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 3);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination grouped state', async () => {
|
it('should search with the destination grouped state', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -164,10 +162,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an ID Origin', async () => {
|
it('should search in smart-table with an ID Origin', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableId, "13");
|
await page.write(selectors.ticketFuture.tableId, '13');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 2);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 2);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -176,10 +174,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an ID Destination', async () => {
|
it('should search in smart-table with an ID Destination', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableTfId, "12");
|
await page.write(selectors.ticketFuture.tableTfId, '12');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -188,7 +186,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an IPT Origin', async () => {
|
it('should search in smart-table with an IPT Origin', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical');
|
await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
@ -199,7 +197,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an IPT Destination', async () => {
|
it('should search in smart-table with an IPT Destination', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical');
|
await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
@ -210,10 +208,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with especified Lines', async () => {
|
it('should search in smart-table with especified Lines', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLines, "0");
|
await page.write(selectors.ticketFuture.tableLines, '0');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -222,8 +220,8 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLines, "1");
|
await page.write(selectors.ticketFuture.tableLines, '1');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -232,10 +230,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with especified Liters', async () => {
|
it('should search in smart-table with especified Liters', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLiters, "0");
|
await page.write(selectors.ticketFuture.tableLiters, '0');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -244,8 +242,8 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLiters, "28");
|
await page.write(selectors.ticketFuture.tableLiters, '28');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -254,13 +252,13 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check the three last tickets and move to the future', async () => {
|
it('should check the three last tickets and move to the future', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.multiCheck);
|
await page.waitToClick(selectors.ticketFuture.multiCheck);
|
||||||
await page.waitToClick(selectors.ticketFuture.firstCheck);
|
await page.waitToClick(selectors.ticketFuture.firstCheck);
|
||||||
await page.waitToClick(selectors.ticketFuture.moveButton);
|
await page.waitToClick(selectors.ticketFuture.moveButton);
|
||||||
await page.waitToClick(selectors.ticketFuture.acceptButton);
|
await page.waitToClick(selectors.ticketFuture.acceptButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('Tickets moved successfully!');
|
expect(message.text).toContain('Tickets moved successfully!');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,9 +12,10 @@ export default class Component extends EventEmitter {
|
||||||
* @param {HTMLElement} $element The main component element
|
* @param {HTMLElement} $element The main component element
|
||||||
* @param {$rootScope.Scope} $scope The element scope
|
* @param {$rootScope.Scope} $scope The element scope
|
||||||
* @param {Function} $transclude The transclusion function
|
* @param {Function} $transclude The transclusion function
|
||||||
|
* @param {Function} $location The location function
|
||||||
*/
|
*/
|
||||||
constructor($element, $scope, $transclude) {
|
constructor($element, $scope, $transclude, $location) {
|
||||||
super();
|
super($element, $scope, $transclude, $location);
|
||||||
this.$ = $scope;
|
this.$ = $scope;
|
||||||
|
|
||||||
if (!$element) return;
|
if (!$element) return;
|
||||||
|
@ -164,7 +165,7 @@ export default class Component extends EventEmitter {
|
||||||
$transclude.$$boundTransclude.$$slots[slot];
|
$transclude.$$boundTransclude.$$slots[slot];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component.$inject = ['$element', '$scope'];
|
Component.$inject = ['$element', '$scope', '$location', '$state'];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Automatically adds the most used services to the prototype, so they are
|
* Automatically adds the most used services to the prototype, so they are
|
||||||
|
|
|
@ -23,7 +23,10 @@ export default class Auth {
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
let criteria = {
|
let criteria = {
|
||||||
to: state => state.name != 'login'
|
to: state => {
|
||||||
|
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
|
||||||
|
return !outLayout.some(ol => ol == state.name);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
this.$transitions.onStart(criteria, transition => {
|
this.$transitions.onStart(criteria, transition => {
|
||||||
if (this.loggedIn)
|
if (this.loggedIn)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<vn-layout
|
<vn-layout
|
||||||
ng-if="$ctrl.showLayout">
|
ng-if="$ctrl.showLayout">
|
||||||
</vn-layout>
|
</vn-layout>
|
||||||
<ui-view
|
<vn-out-layout
|
||||||
name="login"
|
|
||||||
ng-if="!$ctrl.showLayout">
|
ng-if="!$ctrl.showLayout">
|
||||||
</ui-view>
|
</vn-out-layout>
|
||||||
<vn-snackbar vn-id="snackbar"></vn-snackbar>
|
<vn-snackbar vn-id="snackbar"></vn-snackbar>
|
||||||
<vn-debug-info></vn-debug-info>
|
<vn-debug-info></vn-debug-info>
|
||||||
|
|
|
@ -9,13 +9,20 @@ import Component from 'core/lib/component';
|
||||||
* @property {SideMenu} rightMenu The left menu, if it's present
|
* @property {SideMenu} rightMenu The left menu, if it's present
|
||||||
*/
|
*/
|
||||||
export default class App extends Component {
|
export default class App extends Component {
|
||||||
|
constructor($element, $, $location, $state) {
|
||||||
|
super($element, $, $location, $state);
|
||||||
|
this.$location = $location;
|
||||||
|
this.$state = $state;
|
||||||
|
}
|
||||||
|
|
||||||
$postLink() {
|
$postLink() {
|
||||||
alexm marked this conversation as resolved
Outdated
juan
commented
Açò funciona en tots els casos? Que passa si un estat conte login? Açò funciona en **tots** els casos? Que passa si un estat conte login?
|
|||||||
this.vnApp.logger = this;
|
this.vnApp.logger = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showLayout() {
|
get showLayout() {
|
||||||
let state = this.$state.current.name;
|
const state = this.$state.current.name || this.$location.$$path.substring(1).replace('/', '.');
|
||||||
return state && state != 'login';
|
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
|
||||||
|
return state && !outLayout.some(ol => ol == state);
|
||||||
}
|
}
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
|
|
|
@ -5,7 +5,10 @@ import './descriptor-popover';
|
||||||
import './home/home';
|
import './home/home';
|
||||||
import './layout';
|
import './layout';
|
||||||
import './left-menu/left-menu';
|
import './left-menu/left-menu';
|
||||||
|
import './login/index';
|
||||||
import './login/login';
|
import './login/login';
|
||||||
|
import './login/recover-password';
|
||||||
|
import './login/reset-password';
|
||||||
import './module-card';
|
import './module-card';
|
||||||
import './module-main';
|
import './module-main';
|
||||||
import './side-menu/side-menu';
|
import './side-menu/side-menu';
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="box">
|
||||||
|
<img src="./logo.svg"/>
|
||||||
|
<form name="form">
|
||||||
|
<ui-view></ui-view>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -0,0 +1,16 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Component from 'core/lib/component';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default class OutLayout extends Component {
|
||||||
|
constructor($element, $scope) {
|
||||||
|
super($element, $scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutLayout.$inject = ['$element', '$scope'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnOutLayout', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: OutLayout
|
||||||
|
});
|
|
@ -2,3 +2,7 @@ User: User
|
||||||
Password: Password
|
Password: Password
|
||||||
Do not close session: Do not close session
|
Do not close session: Do not close session
|
||||||
Enter: Enter
|
Enter: Enter
|
||||||
|
Password requirements: >
|
||||||
|
The password must have at least {{ length }} length characters,
|
||||||
|
{{nAlpha}} alphabetic characters, {{nUpper}} capital letters, {{nDigits}}
|
||||||
|
digits and {{nPunct}} symbols (Ex: $%&.)
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
User: Usuario
|
User: Usuario
|
||||||
Password: Contraseña
|
Password: Contraseña
|
||||||
|
Email: Correo electrónico
|
||||||
Do not close session: No cerrar sesión
|
Do not close session: No cerrar sesión
|
||||||
Enter: Entrar
|
Enter: Entrar
|
||||||
|
I do not remember my password: No recuerdo mi contraseña
|
||||||
|
Recover password: Recuperar contraseña
|
||||||
|
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
|
||||||
|
Notification sent!: ¡Notificación enviada!
|
||||||
|
Reset password: Restrablecer contraseña
|
||||||
|
New password: Nueva contraseña
|
||||||
|
Repeat password: Repetir contraseña
|
||||||
|
Password requirements: >
|
||||||
|
La contraseña debe tener al menos {{ length }} caracteres de longitud,
|
||||||
|
{{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}}
|
||||||
|
dígitos y {{nPunct}} símbolos (Ej: $%&.)
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<div class="box">
|
<vn-textfield
|
||||||
<img src="./logo.svg"/>
|
|
||||||
<form name="form" ng-submit="$ctrl.submit()">
|
|
||||||
<vn-textfield
|
|
||||||
label="User"
|
label="User"
|
||||||
ng-model="$ctrl.user"
|
ng-model="$ctrl.user"
|
||||||
vn-id="userField"
|
vn-id="userField"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Password"
|
label="Password"
|
||||||
ng-model="$ctrl.password"
|
ng-model="$ctrl.password"
|
||||||
type="password">
|
type="password">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-check
|
<vn-check
|
||||||
label="Do not close session"
|
label="Do not close session"
|
||||||
ng-model="$ctrl.remember"
|
ng-model="$ctrl.remember"
|
||||||
name="remember">
|
name="remember">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<vn-submit label="Enter"></vn-submit>
|
<vn-submit label="Enter" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
<div class="spinner-wrapper">
|
<div class="spinner-wrapper">
|
||||||
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="vn-pt-lg">
|
||||||
|
<a ui-sref="recoverPassword" translate>
|
||||||
|
I do not remember my password
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<h5 class="vn-mb-md vn-mt-lg" translate>Recover password</h5>
|
||||||
|
<vn-textfield
|
||||||
|
label="Email"
|
||||||
|
ng-model="$ctrl.email"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<div
|
||||||
|
class="text-secondary"
|
||||||
|
translate>
|
||||||
|
We will sent you an email to recover your password
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<vn-submit label="Recover password" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,37 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default class Controller {
|
||||||
|
constructor($scope, $element, $http, vnApp, $translate, $state) {
|
||||||
|
Object.assign(this, {
|
||||||
|
$scope,
|
||||||
|
$element,
|
||||||
|
$http,
|
||||||
|
vnApp,
|
||||||
|
$translate,
|
||||||
|
$state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
goToLogin() {
|
||||||
|
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
|
||||||
|
this.$state.go('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
const params = {
|
||||||
|
email: this.email
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$http.post('Accounts/recoverPassword', params)
|
||||||
|
.then(() => {
|
||||||
|
this.goToLogin();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnRecoverPassword', {
|
||||||
|
template: require('./recover-password.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
<h5 class="vn-mb-md vn-mt-lg" translate>Reset password</h5>
|
||||||
|
<vn-textfield
|
||||||
|
label="New password"
|
||||||
|
ng-model="$ctrl.newPassword"
|
||||||
|
type="password"
|
||||||
|
info="{{'Password requirements' | translate:$ctrl.passRequirements}}"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Repeat password"
|
||||||
|
ng-model="$ctrl.repeatPassword"
|
||||||
|
type="password">
|
||||||
|
</vn-textfield>
|
||||||
|
<div class="footer">
|
||||||
|
<vn-submit label="Reset password" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,48 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
alexm marked this conversation as resolved
juan
commented
Perque gastes axios y no el servei $http? Perque gastes axios y no el servei $http?
|
|||||||
|
export default class Controller {
|
||||||
|
constructor($scope, $element, $http, vnApp, $translate, $state, $location) {
|
||||||
|
Object.assign(this, {
|
||||||
|
$scope,
|
||||||
|
$element,
|
||||||
|
$http,
|
||||||
|
vnApp,
|
||||||
|
$translate,
|
||||||
|
$state,
|
||||||
|
$location
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
this.$http.get('UserPasswords/findOne')
|
||||||
|
.then(res => {
|
||||||
|
this.passRequirements = res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (!this.newPassword)
|
||||||
|
throw new UserError(`You must enter a new password`);
|
||||||
|
if (this.newPassword != this.repeatPassword)
|
||||||
|
throw new UserError(`Passwords don't match`);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: this.$location.$$search.access_token
|
||||||
|
};
|
||||||
|
|
||||||
|
const newPassword = this.newPassword;
|
||||||
|
|
||||||
|
this.$http.post('users/reset-password', {newPassword}, {headers})
|
||||||
|
.then(() => {
|
||||||
|
this.vnApp.showSuccess(this.$translate.instant('Password changed!'));
|
||||||
|
this.$state.go('login');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnResetPassword', {
|
||||||
|
template: require('./reset-password.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -1,6 +1,31 @@
|
||||||
@import "variables";
|
@import "variables";
|
||||||
|
|
||||||
vn-login {
|
vn-login,
|
||||||
|
vn-reset-password,
|
||||||
|
vn-recover-password{
|
||||||
|
.footer {
|
||||||
|
margin-top: 32px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
& > .vn-submit {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .spinner-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
top: 3px;
|
||||||
|
right: -8px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vn-out-layout{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -39,28 +64,17 @@ vn-login {
|
||||||
white-space: inherit;
|
white-space: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > .footer {
|
}
|
||||||
margin-top: 32px;
|
|
||||||
|
h5{
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-secondary{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
& > vn-submit {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
& > input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& > .spinner-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
top: 3px;
|
|
||||||
right: -8px;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
|
@ -71,4 +85,8 @@ vn-login {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a{
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ function $exceptionHandler(vnApp, $window, $state, $injector) {
|
||||||
|
|
||||||
switch (exception.status) {
|
switch (exception.status) {
|
||||||
case 401:
|
case 401:
|
||||||
if ($state.current.name != 'login') {
|
if (!$state.current.name.includes('login')) {
|
||||||
messageT = 'Session has expired';
|
messageT = 'Session has expired';
|
||||||
let params = {continue: $window.location.hash};
|
let params = {continue: $window.location.hash};
|
||||||
$state.go('login', params);
|
$state.go('login', params);
|
||||||
|
|
|
@ -9,9 +9,17 @@ function config($stateProvider, $urlRouterProvider) {
|
||||||
.state('login', {
|
.state('login', {
|
||||||
url: '/login?continue',
|
url: '/login?continue',
|
||||||
description: 'Login',
|
description: 'Login',
|
||||||
views: {
|
template: '<vn-login></vn-login>'
|
||||||
login: {template: '<vn-login></vn-login>'}
|
})
|
||||||
}
|
.state('recoverPassword', {
|
||||||
|
url: '/recover-password',
|
||||||
|
description: 'Recover-password',
|
||||||
|
template: '<vn-recover-password>asd</vn-recover-password>'
|
||||||
|
})
|
||||||
|
.state('resetPassword', {
|
||||||
|
url: '/reset-password',
|
||||||
|
description: 'Reset-password',
|
||||||
|
template: '<vn-reset-password></vn-reset-password>'
|
||||||
})
|
})
|
||||||
.state('home', {
|
.state('home', {
|
||||||
url: '/',
|
url: '/',
|
||||||
|
|
|
@ -131,12 +131,15 @@
|
||||||
"Fichadas impares": "Odd signs",
|
"Fichadas impares": "Odd signs",
|
||||||
"Descanso diario 9h.": "Daily rest 9h.",
|
"Descanso diario 9h.": "Daily rest 9h.",
|
||||||
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
|
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
|
||||||
|
"Verify email": "Verify email",
|
||||||
|
"Click on the following link to verify this email. If you haven't requested this email, just ignore it": "Click on the following link to verify this email. If you haven't requested this email, just ignore it",
|
||||||
"Password does not meet requirements": "Password does not meet requirements",
|
"Password does not meet requirements": "Password does not meet requirements",
|
||||||
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
|
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
|
||||||
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
|
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
|
||||||
"Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*",
|
"Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*",
|
||||||
"You don't have grant privilege": "You don't have grant privilege",
|
"You don't have grant privilege": "You don't have grant privilege",
|
||||||
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
|
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
|
||||||
|
"Email verify": "Email verify",
|
||||||
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
|
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
|
||||||
"Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production",
|
"Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production",
|
||||||
"Receipt's bank was not found": "Receipt's bank was not found",
|
"Receipt's bank was not found": "Receipt's bank was not found",
|
||||||
|
|
|
@ -245,6 +245,8 @@
|
||||||
"Already has this status": "Ya tiene este estado",
|
"Already has this status": "Ya tiene este estado",
|
||||||
"There aren't records for this week": "No existen registros para esta semana",
|
"There aren't records for this week": "No existen registros para esta semana",
|
||||||
"Empty data source": "Origen de datos vacio",
|
"Empty data source": "Origen de datos vacio",
|
||||||
|
"Email verify": "Correo de verificación",
|
||||||
|
"Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment",
|
||||||
"Receipt's bank was not found": "No se encontró el banco del recibo",
|
"Receipt's bank was not found": "No se encontró el banco del recibo",
|
||||||
"This receipt was not compensated": "Este recibo no ha sido compensado",
|
"This receipt was not compensated": "Este recibo no ha sido compensado",
|
||||||
"Client's email was not found": "No se encontró el email del cliente"
|
"Client's email was not found": "No se encontró el email del cliente"
|
||||||
|
|
|
@ -30,5 +30,13 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,11 @@ import ngModule from '../module';
|
||||||
import Section from 'salix/components/section';
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
export default class Controller extends Section {
|
export default class Controller extends Section {
|
||||||
|
$onInit() {
|
||||||
|
if (this.$params.emailConfirmed)
|
||||||
|
this.vnApp.showSuccess(this.$t('Email verified successfully!'));
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.$.watcher.submit()
|
this.$.watcher.submit()
|
||||||
.then(() => this.card.reload());
|
.then(() => this.card.reload());
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Email verified successfully!: Correo verificado correctamente!
|
|
@ -74,7 +74,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/basic-data",
|
"url": "/basic-data?emailConfirmed",
|
||||||
"state": "account.card.basicData",
|
"state": "account.card.basicData",
|
||||||
"component": "vn-user-basic-data",
|
"component": "vn-user-basic-data",
|
||||||
"description": "Basic data",
|
"description": "Basic data",
|
||||||
|
|
|
@ -8,10 +8,12 @@ module.exports = {
|
||||||
this.transporter = nodemailer.createTransport(config.smtp);
|
this.transporter = nodemailer.createTransport(config.smtp);
|
||||||
},
|
},
|
||||||
|
|
||||||
send(options) {
|
async send(options) {
|
||||||
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const notProductionError = {message: 'This not production, this email not sended'};
|
||||||
alexm
commented
He modificado esta parte porque como esta diseñado print. Solo inserta en mail(a modo de log) cuando realmente se envia el correo (producción). Haciendo este cambio podemos ver los logs tanto en local como en test. Aunque al final no he usado esta funcionalidad para los test creo que es util para hacer pruebas en test He modificado esta parte porque como esta diseñado print. Solo inserta en mail(a modo de log) cuando realmente se envia el correo (producción). Haciendo este cambio podemos ver los logs tanto en local como en test.
Aunque al final no he usado esta funcionalidad para los test creo que es util para hacer pruebas en test
|
|||||||
|
await this.mailLog(options, notProductionError);
|
||||||
if (!config.smtp.auth.user)
|
if (!config.smtp.auth.user)
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
|
|
||||||
|
@ -24,6 +26,11 @@ module.exports = {
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}).finally(async() => {
|
}).finally(async() => {
|
||||||
|
await this.mailLog(options, error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async mailLog(options, error) {
|
||||||
const attachments = [];
|
const attachments = [];
|
||||||
if (options.attachments) {
|
if (options.attachments) {
|
||||||
for (let attachment of options.attachments) {
|
for (let attachment of options.attachments) {
|
||||||
|
@ -37,6 +44,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileNames = attachments.join(',\n');
|
const fileNames = attachments.join(',\n');
|
||||||
|
|
||||||
await db.rawSql(`
|
await db.rawSql(`
|
||||||
INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status)
|
INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status)
|
||||||
VALUES (?, ?, 1, ?, ?, ?, ?)`, [
|
VALUES (?, ?, 1, ?, ?, ?, ?)`, [
|
||||||
|
@ -47,6 +55,6 @@ module.exports = {
|
||||||
fileNames,
|
fileNames,
|
||||||
error && error.message || 'Sent'
|
error && error.message || 'Sent'
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
const Stylesheet = require(`vn-print/core/stylesheet`);
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const vnPrintPath = path.resolve('print');
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${vnPrintPath}/common/css/spacing.css`,
|
||||||
|
`${vnPrintPath}/common/css/misc.css`,
|
||||||
|
`${vnPrintPath}/common/css/layout.css`,
|
||||||
|
`${vnPrintPath}/common/css/email.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.external-link {
|
||||||
|
border: 2px dashed #8dba25;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<p>
|
||||||
|
{{ $t(`click`) }}
|
||||||
|
<a :href="url">{{ $t('subject') }}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'email-verify',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: [String],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
subject: Email Verify
|
||||||
|
title: Email Verify
|
||||||
|
click: Click on the following link to verify this email. If you haven't requested this email, just ignore it
|
|
@ -0,0 +1,3 @@
|
||||||
|
subject: Verificar correo
|
||||||
|
title: Verificar correo
|
||||||
|
click: Pulsa en el siguiente link para verificar este correo. Si no has pedido este correo, simplemente ignóralo
|
|
@ -0,0 +1,13 @@
|
||||||
|
const Stylesheet = require(`vn-print/core/stylesheet`);
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const vnPrintPath = path.resolve('print');
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${vnPrintPath}/common/css/spacing.css`,
|
||||||
|
`${vnPrintPath}/common/css/misc.css`,
|
||||||
|
`${vnPrintPath}/common/css/layout.css`,
|
||||||
|
`${vnPrintPath}/common/css/email.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.external-link {
|
||||||
|
border: 2px dashed #8dba25;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
subject: Recuperar contraseña
|
||||||
|
title: Recuperar contraseña
|
||||||
|
Click on the following link to change your password.: Pulsa en el siguiente link para cambiar tu contraseña.
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<p>
|
||||||
|
{{ $t('Click on the following link to change your password.') }}
|
||||||
|
<a :href="url">{{ $t('subject') }}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'recover-password',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: [String],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
He dejado esta ruta(recoverPassword) porque si se llama directamente resetPassword y el correo que se le pasa no pertenece a un usuario, devuelve un error al frontend.
Usando una ruta con try catch, hacemos que no devuelva nunca error y asi no pueden saber si ese correo es de un usuario nuestro o no.
Nomes deuria de ignorar el error de tipo "usuario no existe", tots els demes deuria de rellançarlos