UNPKG

passwordless-postgrestore

Version:
196 lines (177 loc) 6.8 kB
'use strict'; var util = require('util'); var bcrypt = require('bcrypt'); var TokenStore = require('passwordless-tokenstore'); var pg = require('pg'); /** * Constructor of PostgreStore * @param {String} conString URI as defined by the PostgreSQL specification. Please * check the documentation for details: * https://github.com/brianc/node-postgres * @param {Object} [options] Combines both the options for the PostgreClient as well * as the options for PostgreStore. For the PostgreClient options please refer back to * the documentation. PostgreStore understands the following options: * (1) { pgstore: { table: string }} to change the name of the table used. Defaults to: 'passwordless' * (2) { pgstore: { pgPoolSize: integer }} to change the pool size of PostgreSQL client. Defaults to: 10 * @constructor */ function PostgreStore(conString, options) { if(!conString){ throw new Error('Connection String is missing.'); } this._options = options || {}; this._options.pgstore = this._options.pgstore || {}; this._table = this._options.pgstore.table || 'passwordless'; pg.defaults.poolSize = this._options.pgstore.pgPoolSize || 10; delete this._options.pgstore; var self = this; pg.connect(conString, function(err, client, done) { if(err) { throw new Error('Could not connect to Postgres database, with error : ' + err); } self._client = client; self._pgDone = done; }); } util.inherits(PostgreStore, TokenStore); /** * Checks if the provided token / user id combination exists and is * valid in terms of time-to-live. If yes, the method provides the * the stored referrer URL if any. * @param {String} token to be authenticated * @param {String} uid Unique identifier of an user * @param {Function} callback in the format (error, valid, referrer). * In case of error, error will provide details, valid will be false and * referrer will be null. If the token / uid combination was not found * found, valid will be false and all else null. Otherwise, valid will * be true, referrer will (if provided when the token was stored) the * original URL requested and error will be null. */ PostgreStore.prototype.authenticate = function(token, uid, callback) { if(!token || !uid || !callback) { throw new Error('TokenStore:authenticate called with invalid parameters'); } var self = this; self._client.query('SELECT * FROM ' + self._table + ' WHERE uid=$1',[uid], function(err, result) { if(err) { return callback(err, false, null); } else if(!result || !result.rows || !result.rows.length || (result && result.rows && result.rows.length > 1)) { return callback(null, false, null); } else if(Date.now() > result.rows[0].ttl) { callback(null, false, null); } else { bcrypt.compare(token, result.rows[0].token, function(err, res) { if(err) { callback(err, false, null); } else if(res) { callback(null, true, result.rows[0].origin); } else { callback(null, false, null); } }); } }); }; /** * Stores a new token / user ID combination or updates the token of an * existing user ID if that ID already exists. Hence, a user can only * have one valid token at a time * @param {String} token Token that allows authentication of _uid_ * @param {String} uid Unique identifier of an user * @param {Number} msToLive Validity of the token in ms * @param {String} originUrl Originally requested URL or null * @param {Function} callback Called with callback(error) in case of an * error or as callback() if the token was successully stored / updated */ PostgreStore.prototype.storeOrUpdate = function(token, uid, msToLive, originUrl, callback) { if(!token || !uid || !msToLive || !callback || !isNumber(msToLive)) { throw new Error('TokenStore:storeOrUpdate called with invalid parameters'); } var self = this; bcrypt.hash(token, 10, function(err, hashedToken) { if(err) { return callback(err); } self._client.query('INSERT INTO ' + self._table + '(uid,token, origin, ttl) VALUES($1, $2, $3, $4)',[uid, hashedToken, originUrl, (Date.now() + msToLive)], function(err) { if(err){ self._client.query('UPDATE ' + self._table + ' SET token=$1, origin=$2, ttl=$3 WHERE uid=$4',[hashedToken, originUrl, (Date.now() + msToLive), uid], function(err) { if(err){ callback(err); } else{ callback(); } //self._client.end(); }); } else{ callback(); } }); }); }; /** * Invalidates and removes a user and the linked token * @param {String} uid user ID * @param {Function} callback called with callback(error) in case of an * error or as callback() if the uid was successully invalidated */ PostgreStore.prototype.invalidateUser = function(uid, callback) { if(!uid || !callback) { throw new Error('TokenStore:invalidateUser called with invalid parameters'); } this._client.query('DELETE FROM ' + this._table + ' WHERE uid=$1',[uid], function(err) { if(err){ callback(err); } else{ callback(); } }); }; /** * Removes and invalidates all token * @param {Function} callback Called with callback(error) in case of an * error or as callback() otherwise */ PostgreStore.prototype.clear = function(callback) { if(!callback) { throw new Error('TokenStore:clear called with invalid parameters'); } this._client.query('DELETE FROM ' + this._table, function(err) { if(err){ callback(err); } else{ callback(); } }); }; /** * Number of tokens stored (no matter the validity) * @param {Function} callback Called with callback(null, count) in case * of success or with callback(error) in case of an error */ PostgreStore.prototype.length = function(callback) { if(!callback) { throw new Error('TokenStore:length called with invalid parameters'); } this._client.query('SELECT COUNT(uid) FROM ' + this._table, function(err, result) { if(err){ callback(err); } else{ callback(null, parseInt(result.rows[0].count)); } }); }; PostgreStore.prototype.disconnect = function() { if(!this._pgDone) { this._pgDone(); } }; function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } module.exports = PostgreStore;