"use strict" ;
const Promise = require ( "bluebird" ) ;
const crypto = require ( "crypto" ) ;
const nanoid = require ( "nanoid" ) ;
const defaultValue = require ( "default-value" ) ;
const databaseError = require ( "database-error" ) ;
const errors = require ( "../errors" ) ;
const createDatabaseModule = require ( "../db-module" ) ;
function tokensMatch ( a , b ) {
return crypto . timingSafeEqual ( Buffer . from ( a ) , Buffer . from ( b ) ) ;
}
module . exports = function ( { knex } ) {
return createDatabaseModule ( {
create : function ( { name , deviceId , userId , token } , tx = knex ) {
return Promise . try ( ( ) => {
let token _ = defaultValue ( token , nanoid ( ) ) ;
let deviceId _ = defaultValue ( deviceId , nanoid ( ) ) ;
return tx ( "devices" ) . insert ( {
device _id : deviceId _ ,
user _id : userId ,
name : name ,
token : token _ ,
} ) . returning ( "*" ) ;
} ) . then ( ( results ) => {
return results [ 0 ] ;
} ) ;
} ,
byDeviceId : function ( { deviceId } , tx = knex ) {
return Promise . try ( ( ) => {
return tx ( "devices" ) . first ( ) . where ( { device _id : deviceId } ) ;
} ) . then ( ( result ) => {
if ( result != null ) {
return result ;
} else {
throw new errors . NotFound ( "The specified device ID does not exist" ) ;
}
} ) ;
} ,
byDeviceIdAndToken : function ( { deviceId , token } , _tx = knex ) {
/* NOTE: This is intentionally a separate method as opposed to conditional logic in byDeviceId, to prevent cases where a bug results in a user-specified token not being passed in - which would fail unsafely by allowing any token, if conditional logic were used. */
return Promise . try ( ( ) => {
if ( token != null ) {
return this . byDeviceId ( { deviceId } ) ;
} else {
throw new errors . MissingAccessToken ( "No access token was specified" ) ;
}
} ) . then ( ( device ) => {
if ( tokensMatch ( token , device . token ) ) {
return device ;
} else {
throw new errors . InvalidAccessToken ( "Specified token was invalid" ) ;
}
} ) ;
} ,
updateToken : function ( { userId , deviceId , token } , tx = knex ) {
return Promise . try ( ( ) => {
return tx ( "devices" )
. update ( { token : token } )
. where ( {
device _id : deviceId ,
user _id : userId
} )
. returning ( "*" ) ;
} ) . then ( ( results ) => {
if ( results . length > 0 ) {
return results [ 0 ] ;
} else {
throw new errors . NotFound ( "Specified Device ID does not exist" ) ;
}
} ) ;
} ,
createDeviceSession : function ( { name , deviceId , userId } , _tx = knex ) {
return Promise . try ( ( ) => {
let token = nanoid ( ) ;
return Promise . try ( ( ) => {
return this . create ( { name , deviceId , userId , token } ) ;
} ) . catch ( { name : "UniqueConstraintViolationError" , table : "devices" , column : "device_id" } , ( _error ) => {
return this . updateToken ( { userId , deviceId , token } ) ;
} ) . then ( ( device ) => {
/* FIXME */
} ) ;
} ) ;
}
} ) ;
} ;