292 lines
9.4 KiB
JavaScript
292 lines
9.4 KiB
JavaScript
const vnModel = require('vn-loopback/common/models/vn-model');
|
|
const {Email} = require('vn-print');
|
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
|
const LoopBackContext = require('loopback-context');
|
|
const UserError = require('vn-loopback/util/user-error');
|
|
|
|
module.exports = function(Self) {
|
|
vnModel(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/privileges')(Self);
|
|
require('../methods/vn-user/validate-auth')(Self);
|
|
require('../methods/vn-user/renew-token')(Self);
|
|
require('../methods/vn-user/update-user')(Self);
|
|
|
|
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
|
|
|
// Validations
|
|
|
|
Self.validatesFormatOf('email', {
|
|
message: 'Invalid email',
|
|
allowNull: true,
|
|
allowBlank: false,
|
|
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
|
|
});
|
|
|
|
Self.validatesUniquenessOf('name', {
|
|
message: `A client with that Web User name already exists`
|
|
});
|
|
|
|
Self.remoteMethod('getCurrentUserData', {
|
|
description: 'Gets the current user data',
|
|
accepts: [
|
|
{
|
|
arg: 'ctx',
|
|
type: 'Object',
|
|
http: {source: 'context'}
|
|
}
|
|
],
|
|
returns: {
|
|
type: 'Object',
|
|
root: true
|
|
},
|
|
http: {
|
|
verb: 'GET',
|
|
path: '/getCurrentUserData'
|
|
}
|
|
});
|
|
|
|
Self.getCurrentUserData = async function(ctx) {
|
|
let userId = ctx.req.accessToken.userId;
|
|
return await Self.findById(userId, {
|
|
fields: ['id', 'name', 'nickname']
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Checks if user has a role.
|
|
*
|
|
* @param {Integer} userId The user id
|
|
* @param {String} name The role name
|
|
* @param {Object} options Options
|
|
* @return {Boolean} %true if user has the role, %false otherwise
|
|
*/
|
|
Self.hasRole = async function(userId, name, options) {
|
|
const roles = await Self.getRoles(userId, options);
|
|
return roles.some(role => role == name);
|
|
};
|
|
|
|
/**
|
|
* Get all user roles.
|
|
*
|
|
* @param {Integer} userId The user id
|
|
* @param {Object} options Options
|
|
* @return {Object} User role list
|
|
*/
|
|
Self.getRoles = async(userId, options) => {
|
|
const result = await Self.rawSql(
|
|
`SELECT r.name
|
|
FROM account.user u
|
|
JOIN account.roleRole rr ON rr.role = u.role
|
|
JOIN account.role r ON r.id = rr.inheritsFrom
|
|
WHERE u.id = ?`, [userId], options);
|
|
|
|
const roles = [];
|
|
for (const role of result)
|
|
roles.push(role.name);
|
|
|
|
return roles;
|
|
};
|
|
|
|
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 defaultHash = '/reset-password?access_token=$token$';
|
|
const recoverHashes = {
|
|
hedera: 'verificationToken=$token$'
|
|
};
|
|
|
|
const app = info.options?.app;
|
|
let recoverHash = app ? recoverHashes[app] : defaultHash;
|
|
recoverHash = recoverHash.replace('$token$', info.accessToken.id);
|
|
|
|
const user = await Self.app.models.VnUser.findById(info.user.id);
|
|
|
|
const params = {
|
|
recipient: info.email,
|
|
lang: user.lang,
|
|
url: origin + '/#!' + recoverHash
|
|
};
|
|
|
|
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();
|
|
});
|
|
|
|
/**
|
|
* Sign-in validate
|
|
* @param {String} user The user
|
|
* @param {Object} userToken Options
|
|
* @param {Object} token accessToken
|
|
* @param {Object} ctx context
|
|
*/
|
|
Self.signInValidate = async(user, userToken, token, ctx) => {
|
|
const [[key, value]] = Object.entries(Self.userUses(user));
|
|
const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]);
|
|
if (!isOwner) {
|
|
await Self.app.models.SignInLog.create({
|
|
userName: user,
|
|
token: token.id,
|
|
userFk: userToken.id,
|
|
ip: ctx.req.ip,
|
|
owner: isOwner
|
|
});
|
|
throw new UserError('Try again');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Validate login params
|
|
* @param {String} user The user
|
|
* @param {String} password
|
|
* @param {Object} ctx context
|
|
*/
|
|
Self.validateLogin = async function(user, password, ctx) {
|
|
const loginInfo = Object.assign({password}, Self.userUses(user));
|
|
const token = await Self.login(loginInfo, 'user');
|
|
|
|
const userToken = await token.user.get();
|
|
|
|
if (ctx)
|
|
await Self.signInValidate(user, userToken, token, ctx);
|
|
|
|
try {
|
|
await Self.app.models.Account.sync(userToken.name, password);
|
|
} catch (err) {
|
|
console.warn(err);
|
|
}
|
|
|
|
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') {
|
|
cb = options;
|
|
options = undefined;
|
|
}
|
|
|
|
const myOptions = {};
|
|
let tx;
|
|
|
|
if (typeof options == 'object')
|
|
Object.assign(myOptions, options);
|
|
|
|
if (!myOptions.transaction) {
|
|
tx = await Self.beginTransaction({});
|
|
myOptions.transaction = tx;
|
|
}
|
|
options = myOptions;
|
|
|
|
try {
|
|
await Self.rawSql(`CALL account.user_checkPassword(?)`, [newPassword], options);
|
|
await _setPassword.call(this, newPassword, options);
|
|
await this.updateAttribute('passExpired', null, options);
|
|
await Self.app.models.Account.sync(this.name, newPassword, null, options);
|
|
tx && await tx.commit();
|
|
cb && cb();
|
|
} catch (err) {
|
|
tx && await tx.rollback();
|
|
if (cb) cb(err); else throw err;
|
|
}
|
|
};
|
|
|
|
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');
|
|
|
|
Self.userSecurity = async(ctx, userId, options) => {
|
|
const models = Self.app.models;
|
|
const accessToken = ctx?.options?.accessToken || LoopBackContext.getCurrentContext().active.accessToken;
|
|
const ctxToken = {req: {accessToken}};
|
|
|
|
if (userId === accessToken.userId) return;
|
|
|
|
const myOptions = {};
|
|
if (typeof options == 'object')
|
|
Object.assign(myOptions, options);
|
|
|
|
const hasHigherPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'higherPrivileges', myOptions);
|
|
if (hasHigherPrivileges) return;
|
|
|
|
const hasMediumPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'mediumPrivileges', myOptions);
|
|
const user = await models.VnUser.findById(userId, {fields: ['id', 'emailVerified']}, myOptions);
|
|
if (!user.emailVerified && hasMediumPrivileges) return;
|
|
|
|
throw new ForbiddenError();
|
|
};
|
|
|
|
Self.observe('after save', async ctx => {
|
|
const instance = ctx?.instance;
|
|
const newEmail = instance?.email;
|
|
const oldEmail = ctx?.hookState?.oldInstance?.email;
|
|
if (!ctx.isNewInstance && (!newEmail || !oldEmail || newEmail == oldEmail)) return;
|
|
|
|
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 env = process.env.NODE_ENV;
|
|
const liliumUrl = await Self.app.models.Url.findOne({
|
|
where: {
|
|
and: [
|
|
{appName: 'lilium'},
|
|
{environment: env}
|
|
]
|
|
}
|
|
});
|
|
|
|
class Mailer {
|
|
async send(verifyOptions, cb) {
|
|
const url = new URL(verifyOptions.verifyHref);
|
|
if (process.env.NODE_ENV) url.port = '';
|
|
|
|
const params = {
|
|
url: url.href,
|
|
recipient: verifyOptions.to
|
|
};
|
|
|
|
const email = new Email('email-verify', params);
|
|
email.send();
|
|
|
|
cb(null, verifyOptions.to);
|
|
}
|
|
}
|
|
|
|
const options = {
|
|
type: 'email',
|
|
to: newEmail,
|
|
from: {},
|
|
redirect: `${liliumUrl.url}verifyEmail?userId=${instance.id}`,
|
|
template: false,
|
|
mailer: new Mailer,
|
|
host: url[1].split('/')[2],
|
|
port: url[2],
|
|
protocol: url[0],
|
|
user: Self
|
|
};
|
|
|
|
await instance.verify(options, ctx.options);
|
|
});
|
|
};
|