Skip to content

Commit

Permalink
feat(gql): search users wip
Browse files Browse the repository at this point in the history
  • Loading branch information
teocomi committed Jul 25, 2020
1 parent f4d236c commit 9733cc6
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 133 deletions.
122 changes: 68 additions & 54 deletions modules/core/graph/resolvers/user.js
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
}
1 change: 1 addition & 0 deletions modules/core/graph/schemas/user.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
169 changes: 90 additions & 79 deletions modules/core/services/users.js
Original file line number Diff line number Diff line change
@@ -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')
}
}

0 comments on commit 9733cc6

Please sign in to comment.