'use strict' const bcrypt = require( 'bcrypt' ) const crs = require( 'crypto-random-string' ) const root = require( 'app-root-path' ) const knex = require( `${root}/db/knex` ) const Users = ( ) => knex( 'users' ) const Keys = ( ) => knex( 'api_tokens' ) module.exports = { /* Users */ async createUser( user ) { user.id = crs( { length: 10 } ) if ( user.password ) { user.passwordDigest = await bcrypt.hash( user.password, 10 ) delete user.password } let res = await Users( ).returning( 'id' ).insert( user ) return res[ 0 ] }, async getUser( id ) { return Users( ).where( { id: id } ).select( 'id', 'username', 'name', 'email', 'profiles', 'verified' ).first( ) }, 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( userId, password ) { var { passwordDigest } = await Users( ).where( { id: userId } ).select( 'passwordDigest' ).first( ) return bcrypt.compare( password, passwordDigest ) }, async deleteUser( id ) { throw new Error( 'not implemented' ) }, /* Tokens Note: tokens are composed of a 10 char token id and a 32 char token string. The token string is smoked, salted and hashed and stored in the database. */ async createToken( userId, name, scopes, lifespan ) { let tokenId = crs( { length: 10 } ) let tokenString = crs( { length: 32 } ) let tokenHash = await bcrypt.hash( tokenString, 10 ) let lastChars = tokenString.slice( tokenString.length - 6, tokenString.length ) let res = await Keys( ).returning( 'id' ).insert( { id: tokenId, tokenDigest: tokenHash, lastChars: lastChars, owner: userId, name: name, scopes: scopes, lifespan: lifespan } ) return tokenId + tokenString }, async validateToken( tokenString ) { let tokenId = tokenString.slice( 0, 10 ) let tokenContent = tokenString.slice( 10, 42 ) let token = await Keys( ).where( { id: tokenId } ).select( '*' ).first( ) if ( !token ) { return { valid: false } } const timeDiff = Math.abs( Date.now( ) - new Date( token.createdAt ) ) if ( timeDiff > token.lifespan ) { await module.exports.revokeToken( tokenId ) return { valid: false } } let valid = await bcrypt.compare( tokenContent, token.tokenDigest ) if ( valid ) { await Keys( ).where( { id: tokenId } ).update( { lastUsed: knex.fn.now( ) } ) return { valid: true, userId: token.owner, scopes: token.scopes } } else return { valid: false } }, async revokeToken( tokenId ) { tokenId = tokenId.slice( 0, 10 ) await Keys( ).where( { id: tokenId } ).del( ) }, async getUserTokens( userId ) { return Keys( ).where( { owner: userId } ).select( 'id', 'name', 'lastChars', 'scopes', 'createdAt', 'lastUsed' ) } }