UNPKG

@openveo/portal

Version:

OpenVeo Portal gives access to medias exposed by OpenVeo server associated to an OpenVeo Publish plugin

392 lines (331 loc) 12.9 kB
'use strict'; /** * @module portal/providers/UserProvider */ const path = require('path'); const async = require('async'); const nanoid = require('nanoid').nanoid; const crypto = require('crypto'); const openVeoApi = require('@openveo/api'); const configDir = openVeoApi.fileSystem.getConfDir(); const conf = require(path.join(configDir, 'portal/conf.json')); const ResourceFilter = openVeoApi.storages.ResourceFilter; class UserProvider extends openVeoApi.providers.EntityProvider { /** * Defines a UserProvider to get and save back end users. * * @class UserProvider * @extends EntityProvider * @constructor * @param {Storage} storage The storage to use to store users * @see {@link https://github.com/veo-labs/openveo-api|OpenVeo API documentation} for more information about Storage and EntityProvider */ constructor(storage) { super(storage, 'portal_users'); } /** * Gets an internal user by its credentials. * * @param {String} email The email of the user * @param {String} password The password of the user * @param {module:portal/providers/UserProvider~UserProvider~getUserByCredentialsCallback} callback Function to call * when it's done */ getUserByCredentials(email, password, callback) { password = crypto.createHmac('sha256', conf.passwordHashKey).update(password).digest('hex'); this.getOne( new ResourceFilter() .equal('origin', openVeoApi.passport.STRATEGIES.LOCAL) .equal('email', email) .equal('password', password), { exclude: ['password'] }, callback ); } /** * Gets an internal user by its email. * * @param {String} email The email of the user * @param {module:portal/providers/UserProvider~UserProvider~getUserByEmailCallback} callback Function to call when * it's done */ getUserByEmail(email, callback) { this.getOne( new ResourceFilter() .equal('origin', openVeoApi.passport.STRATEGIES.LOCAL) .equal('email', email), { exclude: ['password'] }, callback ); } /** * Adds users. * * @param {Array} users The list of users to store with for each user: * @param {String} users[].name The user name * @param {String} users[].email The user email * @param {String} users[].password The user password * @param {String} users[].passwordValidate The user password validation * @param {String} [users[].id] The user id, generated if not specified * @param {Array} [users[].groups] The user groups ids * @param {Boolean} [users[].locked=false] true to lock the user from edition, false otherwise * @param {module:portal/providers/UserProvider~UserProvider~addCallback} callback Function to call when it's done */ add(users, callback) { const usersToAdd = []; const userEmails = []; for (let user of users) { if (!user.name || !user.email || !user.password) return this.executeCallback(callback, new TypeError('Requires name, email and password to add a user')); // Validate password if (user.password !== user.passwordValidate) return this.executeCallback(callback, new Error('Passwords do not match')); // Validate email if (!openVeoApi.util.isEmailValid(user.email)) return this.executeCallback(callback, new TypeError('Invalid email address: ' + user.email)); userEmails.push(user.email); } // Find users this.getAll( new ResourceFilter() .equal('origin', openVeoApi.passport.STRATEGIES.LOCAL) .in('email', userEmails), { include: ['email'] }, { id: 'desc' }, (error, fetchedUsers) => { if (error) return this.executeCallback(callback, error); for (let user of users) { // Validate email for (let fetchedUser of fetchedUsers) { if (user.email === fetchedUser.email) return this.executeCallback(callback, new Error(`Email "${user.email}" not available`)); } // Encrypt password const password = crypto.createHmac('sha256', conf.passwordHashKey).update(user.password).digest('hex'); usersToAdd.push({ id: user.id || nanoid(), name: user.name, email: user.email, password, locked: user.locked || false, origin: openVeoApi.passport.STRATEGIES.LOCAL, groups: user.groups || [] }); } super.add(usersToAdd, (error, total, addedUsers) => { if (error) return this.executeCallback(callback, error); if (callback) { addedUsers.forEach((addedUser) => { delete addedUser['password']; }); callback(error, total, addedUsers); } }); } ); } /** * Updates an internal user. * * @param {ResourceFilter} [filter] Rules to filter the user to update * @param {Object} data The modifications to perform * @param {String} [data.name] The user name * @param {String} [data.email] The user email * @param {String} [data.password] The user password. Also requires passwordValidate * @param {String} [data.passwordValidate] The user password validation. Also requires password * @param {Array} [data.groups] The user group ids * @param {Boolean} [data.locked] true to lock the user from edition, false otherwise * @param {module:portal/providers/UserProvider~UserProvider~updateOneCallback} callback Function to call when it's * done */ updateOne(filter, data, callback) { const modifications = {}; let total; let user; if (!filter) filter = new ResourceFilter(); filter.equal('origin', openVeoApi.passport.STRATEGIES.LOCAL); // Validate password if (data.password) { if (data.password !== data.passwordValidate) return this.executeCallback(callback, new Error('Passwords does not match')); else { // Encrypt password const password = crypto.createHmac('sha256', conf.passwordHashKey).update(data.password).digest('hex'); modifications.password = password; } } // Validate email if (data.email && !openVeoApi.util.isEmailValid(data.email)) return this.executeCallback(callback, new TypeError('Invalid email address')); // Validate groups if (data.groups) modifications.groups = data.groups; // Validate name if (data.name) modifications.name = data.name; // Validate locked if (typeof data.locked !== 'undefined') modifications.locked = Boolean(data.locked); async.series([ // Get user corresponding to given filter (callback) => { this.getOne( filter, { include: ['id'] }, (error, fetchedUser) => { user = fetchedUser; callback(error); } ); }, // Verify if the email address is not already used (callback) => { if (!data.email) return callback(); this.getUserByEmail(data.email, (error, fetchedUser) => { if (error) return callback(error); if (fetchedUser && fetchedUser.id != user.id) return callback(new Error('Email not available')); modifications.email = data.email; callback(); }); }, // Update user (callback) => { super.updateOne( new ResourceFilter().equal('id', user.id).equal('origin', openVeoApi.passport.STRATEGIES.LOCAL), modifications, (error, totalItems) => { if (error) return callback(error); total = totalItems; callback(); } ); } ], (error) => { this.executeCallback(callback, error, total); }); } /** * Adds external users. * * External users are automatically locked when added. * * @param {Array} users The list of users to add with for each user: * @param {String} users[].name The user name * @param {String} users[].email The user email * @param {String} users[].origin Id of the third party provider system * @param {String} users[].originId The user id in third party provider system * @param {String} [users[].id] The user id, generated if not specified * @param {Array} [users[].originGroups] The user groups in third party provider system * @param {Array} [users[].groups] The user group ids * @param {module:portal/providers/UserProvider~UserProvider~addThirdPartyUsersCallback} callback Function to call * when it's done */ addThirdPartyUsers(users, callback) { const usersToAdd = []; for (let user of users) { if (!user.origin || !user.name || !user.email || !user.originId) { return this.executeCallback( callback, new TypeError('Requires name, email, origin and origin id to add a third party user') ); } if (user.origin === openVeoApi.passport.STRATEGIES.LOCAL) return this.executeCallback(callback, new Error('Third party user origin can\'t be local')); usersToAdd.push({ id: user.id || nanoid(), name: user.name, email: user.email, origin: user.origin, originId: user.originId, originGroups: user.originGroups || [], groups: user.groups || [], locked: true }); } super.add(usersToAdd, callback); } /** * Updates an external user. * * @param {ResourceFilter} [filter] Rules to filter users to update * @param {Object} data The modifications to perform * @param {String} [data.name] The user name * @param {String} [data.email] The user email * @param {Array} [data.originGroups] The user groups in third party provider system * @param {Array} [data.groups] The user group ids * @param {Boolean} [data.locked] true to lock the user from edition, false otherwise * @param {String} origin The user origin (see openVeoApi.passport.STRATEGIES) * @param {module:portal/providers/UserProvider~UserProvider~updateThirdPartyUserCallback} callback Function to call * when it's done */ updateThirdPartyUser(filter, data, origin, callback) { const modifications = {}; if (origin === openVeoApi.passport.STRATEGIES.LOCAL) return this.executeCallback(callback, new Error('Can\'t update a local user with "updateThirdPartyUser"')); if (!filter) filter = new ResourceFilter(); filter.equal('origin', origin); if (data.name) modifications.name = data.name; if (data.email) modifications.email = data.email; if (data.originGroups) modifications.originGroups = data.originGroups; if (data.groups) modifications.groups = data.groups; if (typeof data.locked !== 'undefined') modifications.locked = Boolean(data.locked); this.storage.updateOne(this.location, filter, modifications, (error, total) => { this.executeCallback(callback, error, total); }); } /** * Creates users indexes. * * @param {callback} callback Function to call when it's done with: */ createIndexes(callback) { this.storage.createIndexes(this.location, [ {key: {name: 1}, name: 'byName'}, {key: {name: 'text'}, weights: {name: 1}, name: 'querySearch'} ], (error, result) => { if (result && result.note) process.logger.debug(`Create users indexes : ${result.note}`); callback(error); }); } } module.exports = UserProvider; /** * @callback module:portal/providers/UserProvider~UserProvider~getUserByCredentialsCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Object} user The user */ /** * @callback module:portal/providers/UserProvider~UserProvider~getUserByEmailCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Object} user The user */ /** * @callback module:portal/providers/UserProvider~UserProvider~addCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Number} total The total amount of users inserted * @param {Array} users The list of added users */ /** * @callback module:portal/providers/UserProvider~UserProvider~updateOneCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Number} total 1 if everything went fine */ /** * @callback module:portal/providers/UserProvider~UserProvider~addThirdPartyUsersCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Number} total The total amount of users inserted * @param {Number} users The inserted users */ /** * @callback module:portal/providers/UserProvider~UserProvider~updateThirdPartyUserCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Number} total 1 if everything went fine */