UNPKG

akima-client

Version:

Client side support for the Akima oauth2 framework

410 lines (357 loc) 11.1 kB
"use strict"; const Redis = require("ioredis"); const memoize = require("memoizee"); const pg = require('pg'); let redisClient = null; let pgConString = null; let pgUtil; let getUserRouter = {}; const maxAge = 2000; const immutableCallMaxAge = 60000; function initialize(config) { if (config.type == "redis") redisClient = Redis.createClient(config.port, config.host, {detect_buffers: true}); else { pgConString = config.pgConString; pgUtil = require("./pg_util")(pgConString); doPGSubscribe('token_modified',newPGSubscriberContext(pgConString),function(msg) { let tokenId = msg.payload; memoizedPGGetToken.delete(tokenId); }); } } /**** Copied from akima server pg-based pub/sub fault tolerance. The idea is that the database connection is allowed to break, but the client will continue to re-attempt if so. Do exponential backoff to make it efficient, and max out at 8s re-query to make it react quickly when the connection becomes available. This can operate in parallel with the pg store as it uses a separate and independant client. ****/ function newPGSubscriberContext(connectionString,backoff) { if(backoff == null) backoff = 500; return { subscriber:new pg.Client(connectionString), connectionString:connectionString, backoff:backoff }; } function handlePGSubscriberError(toChannel,withContext,error,callback) { var backoff = 2*withContext.backoff; console.log('subscription error ',error); withContext.subscriber.end(); if(backoff > 8000) backoff = 8000; setTimeout(function() { var newContext = newPGSubscriberContext(withContext.connectionString,backoff);   doPGSubscribe(toChannel,newContext,callback); }, backoff); } function doPGSubscribe(toChannel,withContext,callback) {   withContext.subscriber.connect(function(err) { if(err) handlePGSubscriberError(toChannel,withContext,err,callback); else {  withContext.subscriber.query("LISTEN " + toChannel,function(err) {    if(err) handlePGSubscriberError(toChannel,withContext,err,callback); else {     withContext.subscriber.on('notification',function(msg) { callback(msg); });  withContext.subscriber.on('error',function(error) { handlePGSubscriberError(toChannel,withContext,error,callback); }); }  }); } }); } function redisGetToken(tokenId, callback) { redisClient.get(tokenId, function (err, reply) { if (err) { console.log("Error fetching token information from redis: " + err); return callback(err); } if (reply) { let token = JSON.parse(reply.toString()); if (token) { token.id = tokenId; } callback(null, token); } else { callback(null, null); } }); } function pgGetToken(tokenId, callback) { //console.log("************************** real get token *******************************"); pgUtil.performSingleRowQuery( { "query": "SELECT info FROM tokens WHERE token_id=$1", "queryParameters": [tokenId], "callback": function (err, obj) { if (err) { return callback(err); } let token = null; if (obj) { if (typeof(obj.info) === "string") token = JSON.parse(obj.info); else token = obj.info; token.id = tokenId; } callback(null, token); } }); } const memoizedRedisGetToken = memoize(redisGetToken,{ async:true, maxAge: immutableCallMaxAge }); const memoizedPGGetToken = memoize(pgGetToken,{ async:true, maxAge: immutableCallMaxAge }); function redisSaveToken(tokenId, params, user, callback) { if (tokenId) { memoizedRedisGetToken.delete(tokenId); let v = {"params": params, "user": user}; redisClient.set(tokenId, JSON.stringify(v)); } callback(); } function pgSaveToken(tokenId, params, user, callback) { if (tokenId) { memoizedPGGetToken.delete(tokenId); let o = {tokenId: tokenId, info: JSON.stringify({"params": params, "user": user})}; pgUtil.performObjectSave( { "tableName": "tokens", "primaryKeyName": "token_id", "object": o, "callback": callback }); } else { callback(); } } function pgUpsertToken(tokenId,params,user,callback) { let query; let o = { params:params, user:user }; memoizedPGGetToken.delete(tokenId); query = ` WITH upsert AS ( select $1::text AS token_id, $2::json AS info, $3::timestamp AS expires_at ), update_option AS ( UPDATE tokens SET info = upsert.info, expires_at = upsert.expires_at FROM upsert WHERE tokens.token_id = upsert.token_id ) INSERT INTO tokens (token_id,info,expires_at) SELECT token_id, info, expires_at FROM upsert WHERE NOT EXISTS(SELECT 1 FROM tokens WHERE tokens.token_id = upsert.token_id)` pgUtil.performSingleRowQuery( { "query": query, "queryParameters": [tokenId,o,params.expiresAt], "callback": function(err) { if(typeof err == 'string' && err.match(/duplicate/) != null) callback(); else callback(err); } }); } function redisRemoveToken(tokenId, callback) { if (tokenId) { memoizedRedisGetToken.delete(tokenId); redisClient.del(tokenId); } callback(); } function pgRemoveToken(tokenId, callback) { if (tokenId) { memoizedPGGetToken.delete(tokenId); pgUtil.performObjectDelete( { tableName: "tokens", key: "token_id", id: tokenId, "callback": callback }); } else { callback(); } } function redisGetUser(userId, callback) { redisClient.get(userId, function (err, reply) { if (err) { return callback(err); } if (reply) { user = JSON.parse(reply.toString()); callback(null, user); } else { callback(null, null); } }); } function pgGetUser(userId, callback) { pgUtil.performSingleRowQuery( { "query": "SELECT info FROM users WHERE user_id=$1", "queryParameters": [userId], "callback": function (err, obj) { if (err) { return callback(err); } let user = null; if (obj) { if (typeof(obj) === "string") { user = JSON.parse(obj.info); } else user = obj.info; } callback(null, user); } }); } const memoizedRedisGetUser = memoize(redisGetUser,{ async:true, maxAge: maxAge }); const memoizedPGGetUser = memoize(pgGetUser,{ async:true, maxAge: maxAge }); function pgUpsertUser(userId,info,callback) { let query; memoizedPGGetUser.delete(userId); query = ` WITH upsert AS ( SELECT $1::text AS user_id, $2::json AS info ), update_option AS ( UPDATE users SET info = upsert.info FROM upsert WHERE users.user_id = upsert.user_id ) INSERT INTO users (user_id,info) SELECT user_id,info FROM upsert WHERE NOT EXISTS(SELECT 1 FROM users WHERE users.user_id = upsert.user_id)` pgUtil.performSingleRowQuery( { "query": query, "queryParameters": [userId,info], "callback": function(err) { if(typeof err == 'string' && err.match(/duplicate/) != null) callback(); else callback(err); } }); } function redisUpsertTokenAndUser(tokenId,tokenInfo,profile,callback) { if (userId != null && profile.id != null) { let userId = profile.id; let v = {"params": params, "user": user}; if(memoizedRedisGetToken != null) memoizedRedisGetToken.delete(tokenId); if(memoizedRedisGetUser != null) memoizedRedisGetUser.delete(userId); redisClient.set(tokenId,JSON.stringify(v)); redisClient.set(userId,JSON.stringify(user)); } } function pgUpsertTokenAndUser(tokenId,tokenInfo,userInfo,callback) { let query = "SELECT upsert_token_and_user($1::text,$2::json,$3::text,$4::json)"; let o = { params:tokenInfo, user:userInfo }; pgUtil.performSingleRowQuery( { "query": query, "queryParameters": [tokenId,o,userInfo.id,userInfo], "callback": function(err) { if(err) callback(err); else callback(); } }); } function redisSaveUser(userId, user, callback) { if (userId) { memoizedRedisGetUser.delete(userId); redisClient.set(userId, JSON.stringify(user)); } callback(); } function pgSaveUser(userId, user, callback) { if (userId) { let o = {userId: userId, info: JSON.stringify(user)}; memoizedPGGetUser.delete(userId); pgUtil.performObjectSave( { "tableName": "users", "primaryKeyName": "user_id", "object": o, "callback": callback }); } else { callback(); } } function redisRemoveUser(userId, callback) { if (userId) { memoizedRedisGetUser.delete(userId); redisClient.del(userId); } callback(); } function pgRemoveUser(userId, callback) { if (userId) { memoizedPGGetUser.delete(userId); pgUtil.performObjectDelete( { tableName: "users", key: "user_id", id: userId, "callback": callback }); } else { callback(); } } function getUserRoutingThunk(getUser) { return function(userId,done) { if(getUserRouter[userId] == null) { getUserRouter[userId] = [done]; getUser(userId,function(err,user) { let callbacks = getUserRouter[userId]; getUserRouter[userId] = null; if(user == null) console.log(`user data for userId ${userId} is null, num callbacks = ${callbacks.length}`); for(let i = 0;i < callbacks.length;i++) callbacks[i](err,user); }); } else getUserRouter[userId].push(done); } } module.exports = { initialize: initialize, redis: { saveToken: memoize(redisSaveToken,{ async:true, maxAge: maxAge }), getToken: memoizedRedisGetToken, upsertToken: memoize(redisSaveToken,{ async:true, maxAge: maxAge }), removeToken: memoize(redisRemoveToken,{ async:true, maxAge: maxAge }), saveUser: memoize(redisSaveUser,{ async:true, maxAge: maxAge }), getUser: getUserRoutingThunk(memoizedRedisGetUser), upsertUser: memoize(redisSaveUser,{ async:true, maxAge: maxAge }), removeUser: memoize(redisRemoveUser,{ async:true, maxAge: maxAge }), upsertTokenAndUser: memoize(redisUpsertTokenAndUser,{ async:true, maxAge: maxAge }) }, pg: { saveToken: memoize(pgSaveToken,{ async:true, maxAge: maxAge }), getToken: function(token,done) { memoizedPGGetToken(token,function(err,res) { if(res == null) memoizedPGGetToken.delete(token); done(err,res); }); }, upsertToken: memoize(pgUpsertToken,{ async:true, maxAge: maxAge }), removeToken: memoize(pgRemoveToken,{ async:true, maxAge: maxAge }), saveUser: memoize(pgSaveUser,{ async:true, maxAge: maxAge }), getUser: getUserRoutingThunk(memoizedPGGetUser), upsertUser: memoize(pgUpsertUser,{ async:true, maxAge: maxAge }), removeUser: memoize(pgRemoveUser,{ async:true, maxAge: maxAge }), upsertTokenAndUser: memoize(pgUpsertTokenAndUser,{ async:true, maxAge: maxAge }) } };