diff --git a/modules/core/graph/resolvers/user.js b/modules/core/graph/resolvers/user.js index cb5a6853d4..bf9c0e8228 100644 --- a/modules/core/graph/resolvers/user.js +++ b/modules/core/graph/resolvers/user.js @@ -1,74 +1,88 @@ 'use strict' -const appRoot = require( 'app-root-path' ) -const { ApolloError, AuthenticationError, UserInputError } = require( 'apollo-server-express' ) -const { createUser, getUser, getUserByEmail, getUserRole, updateUser, deleteUser, validatePasssword } = require( '../../services/users' ) -const { createPersonalAccessToken, createAppToken, revokeToken, revokeTokenById, validateToken, getUserTokens } = require( '../../services/tokens' ) -const { validateServerRole, validateScopes, authorizeResolver } = require( `${appRoot}/modules/shared` ) -const setupCheck = require( `${appRoot}/setupcheck` ) -const zxcvbn = require( 'zxcvbn' ) +const appRoot = require('app-root-path') +const { ApolloError, AuthenticationError, UserInputError } = require('apollo-server-express') +const { createUser, getUser, getUserByEmail, getUserRole, updateUser, deleteUser, findUsers, validatePasssword } = require('../../services/users') +const { createPersonalAccessToken, createAppToken, revokeToken, revokeTokenById, validateToken, getUserTokens } = require('../../services/tokens') +const { validateServerRole, validateScopes, authorizeResolver } = require(`${appRoot}/modules/shared`) +const setupCheck = require(`${appRoot}/setupcheck`) +const zxcvbn = require('zxcvbn') module.exports = { - Query: { + Query: { - async _( ) { - return `Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn.` - }, + async _() { + return `Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn.` + }, + + async user(parent, args, context, info) { + + await validateServerRole(context, 'server:user') + + if (!args.id) + await validateScopes(context.scopes, 'profile:read') + else + await validateScopes(context.scopes, 'users:read') + + if (!args.id && !context.userId) { + throw new UserInputError('You must provide an user id.') + } + + return await getUser(args.id || context.userId) + }, - async user( parent, args, context, info ) { + async users(parent, args, context, info) { - await validateServerRole( context, 'server:user' ) + await validateServerRole(context, 'server:user') - if ( !args.id ) - await validateScopes( context.scopes, 'profile:read' ) - else - await validateScopes( context.scopes, 'users:read' ) + if (!args.id) + await validateScopes(context.scopes, 'profile:read') + else + await validateScopes(context.scopes, 'users:read') - if ( !args.id && !context.userId ) { - throw new UserInputError( 'You must provide an user id.' ) - } + return await findUsers(args.query) + }, + + async userPwdStrength(parent, args, context, info) { + let res = zxcvbn(args.pwd) + return { score: res.score, feedback: res.feedback } + } - return await getUser( args.id || context.userId ) }, - async userPwdStrength( parent, args, context, info ) { - let res = zxcvbn( args.pwd ) - return { score: res.score, feedback: res.feedback } - } + User: { - }, + async email(parent, args, context, info) { + // NOTE: we're redacting the field (returning null) rather than throwing a full error which would invalidate the request. + if (context.userId === parent.id) { + try { + await validateScopes(context.scopes, 'profile:email') + return parent.email + } catch (err) { + return null + } + } - User: { + try { + await validateScopes(context.scopes, 'users:email') + return parent.email + } catch (err) { + return null + } + }, - async email( parent, args, context, info ) { - // NOTE: we're redacting the field (returning null) rather than throwing a full error which would invalidate the request. - if ( context.userId === parent.id ) { - try { - await validateScopes( context.scopes, 'profile:email' ) - return parent.email - } catch ( err ) { - return null + async role(parent, args, context, info) { + return await getUserRole(parent.id) } - } - - try { - await validateScopes( context.scopes, 'users:email' ) - return parent.email - } catch ( err ) { - return null - } + }, - async role( parent, args, context, info ) { - return await getUserRole( parent.id ) - } - }, - Mutation: { - async userEdit( parent, args, context, info ) { - await validateServerRole( context, 'server:user' ) - await updateUser( context.userId, args.user ) - return true + Mutation: { + async userEdit(parent, args, context, info) { + await validateServerRole(context, 'server:user') + await updateUser(context.userId, args.user) + return true + } } - } -} +} \ No newline at end of file diff --git a/modules/core/graph/schemas/user.graphql b/modules/core/graph/schemas/user.graphql index 355ec20fd8..2767a7b3f3 100644 --- a/modules/core/graph/schemas/user.graphql +++ b/modules/core/graph/schemas/user.graphql @@ -3,6 +3,7 @@ extend type Query { Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header). """ user( id: String ): User + users(query: String): [User!]! userPwdStrength( pwd: String! ): JSONObject } diff --git a/modules/core/services/users.js b/modules/core/services/users.js index 4473d051ac..e422a9bbf8 100644 --- a/modules/core/services/users.js +++ b/modules/core/services/users.js @@ -1,93 +1,104 @@ 'use strict' -const bcrypt = require( 'bcrypt' ) -const crs = require( 'crypto-random-string' ) -const appRoot = require( 'app-root-path' ) -const knex = require( `${appRoot}/db/knex` ) +const bcrypt = require('bcrypt') +const crs = require('crypto-random-string') +const appRoot = require('app-root-path') +const knex = require(`${appRoot}/db/knex`) -const Users = ( ) => knex( 'users' ) -const ServerRoles = ( ) => knex( 'server_acl' ) +const Users = () => knex('users') +const ServerRoles = () => knex('server_acl') module.exports = { - /* + /* - Users + Users - */ + */ - async createUser( user ) { - let [ { count } ] = await ServerRoles( ).where( { role: 'server:admin' } ).count( ) + async createUser(user) { + let [{ count }] = await ServerRoles().where({ role: 'server:admin' }).count() - user.id = crs( { length: 10 } ) + user.id = crs({ length: 10 }) - if ( user.password ) { - user.passwordDigest = await bcrypt.hash( user.password, 10 ) - } - delete user.password + if (user.password) { + user.passwordDigest = await bcrypt.hash(user.password, 10) + } + delete user.password - let usr = await Users( ).select( 'id' ).where( { email: user.email } ).first( ) - if ( usr ) throw new Error( 'Email taken. Try logging in?' ) + let usr = await Users().select('id').where({ email: user.email }).first() + if (usr) throw new Error('Email taken. Try logging in?') - let res = await Users( ).returning( 'id' ).insert( user ) + let res = await Users().returning('id').insert(user) - if ( parseInt( count ) === 0 ) { - await ServerRoles( ).insert( { userId: res[ 0 ], role: 'server:admin' } ) - } else { - await ServerRoles( ).insert( { userId: res[ 0 ], role: 'server:user' } ) - } + if (parseInt(count) === 0) { + await ServerRoles().insert({ userId: res[0], role: 'server:admin' }) + } else { + await ServerRoles().insert({ userId: res[0], role: 'server:user' }) + } + + return res[0] + }, + + async findOrCreateUser({ user, rawProfile }) { + let existingUser = await Users().select('id').where({ email: user.email }).first() + + if (existingUser) + return existingUser + + user.password = crs({ length: 20 }) + user.verified = true // because we trust the external identity provider, no? + return { id: await module.exports.createUser(user) } + }, + + async getUserById({ userId }) { + let user = await Users().where({ id: userId }).select('*').first() + delete user.passwordDigest + return user + }, + + // TODO: deprecate + async getUser(id) { + let user = await Users().where({ id: id }).select('*').first() + delete user.passwordDigest + return user + }, - return res[ 0 ] - }, - - async findOrCreateUser( { user, rawProfile } ) { - let existingUser = await Users( ).select( 'id' ).where( { email: user.email } ).first( ) - - if ( existingUser ) - return existingUser - - user.password = crs( { length: 20 } ) - user.verified = true // because we trust the external identity provider, no? - return { id: await module.exports.createUser( user ) } - }, - - async getUserById( { userId } ) { - let user = await Users( ).where( { id: userId } ).select( '*' ).first( ) - delete user.passwordDigest - return user - }, - - // TODO: deprecate - async getUser( id ) { - let user = await Users( ).where( { id: id } ).select( '*' ).first( ) - delete user.passwordDigest - return user - }, - - async getUserByEmail( { email } ) { - let user = await Users( ).where( { email: email } ).select( '*' ).first( ) - delete user.passwordDigest - return user - }, - - async getUserRole( id ) { - let { role } = await ServerRoles( ).where( { userId: id } ).select( 'role' ).first( ) - return role - }, - - async updateUser( id, user ) { - delete user.id - delete user.passwordDigest - delete user.password - delete user.email - await Users( ).where( { id: id } ).update( user ) - }, - - async validatePasssword( { email, password } ) { - let { passwordDigest } = await Users( ).where( { email: email } ).select( 'passwordDigest' ).first( ) - return bcrypt.compare( password, passwordDigest ) - }, - - async deleteUser( id ) { - throw new Error( 'not implemented' ) - } -} + async getUserByEmail({ email }) { + let user = await Users().where({ email: email }).select('*').first() + delete user.passwordDigest + return user + }, + + async getUserRole(id) { + let { role } = await ServerRoles().where({ userId: id }).select('role').first() + return role + }, + + async updateUser(id, user) { + delete user.id + delete user.passwordDigest + delete user.password + delete user.email + await Users().where({ id: id }).update(user) + }, + + async findUsers(query) { + + query = "%" + query + "%"; + let users = await Users() + .where('email', 'like', query) + .orWhere('username', 'like', query) + .orWhere('name', 'like', query) + + return users + }, + + async validatePasssword({ email, password }) { + let { passwordDigest } = await Users().where({ email: email }).select('passwordDigest').first() + return bcrypt.compare(password, passwordDigest) + }, + + async deleteUser(id) { + throw new Error('not implemented') + } +} \ No newline at end of file