From ef452943c9fc33f76388b21d4fad959ed7c05f58 Mon Sep 17 00:00:00 2001 From: HarishTeens Date: Sat, 20 Mar 2021 12:02:21 +0530 Subject: [PATCH 1/2] cache jwt token every time a jwt is to be decoded, check in the node cache instead of decoding it everytime using the verifyIdToken method --- sources/apollo/server.js | 27 ++++++++++++++++++++------- sources/helpers/permissions.js | 5 ++++- sources/package.json | 3 ++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/sources/apollo/server.js b/sources/apollo/server.js index 7b7d67e4..5d03c86f 100644 --- a/sources/apollo/server.js +++ b/sources/apollo/server.js @@ -12,7 +12,10 @@ const StoryAPI = require('./stories/story.datasources.js'); const typeDefs = require('./schema.js'); const resolvers = require('./resolvers.js'); const {firebaseApp}=require("../helpers/firebase"); -const {populatePermissions } = require("../helpers/permissions"); +const { populatePermissions } = require("../helpers/permissions"); + +const NodeCache = require( "node-cache" ); +const tokenCache = new NodeCache(); //Datasources @@ -41,13 +44,23 @@ const server = new ApolloServer({ context: async ({ req }) => { if (req.headers && req.headers.authorization) { const idToken=req.headers.authorization; - try { - const decodedToken = await firebaseApp.auth().verifyIdToken(idToken) - const {uid} = decodedToken; - if(decodedToken.mongoID){ - return {uid, permissions: await populatePermissions(decodedToken.mongoID)}; + try { + console.time("firebase"); + + let userToken = tokenCache.get(idToken); + if (userToken === undefined) { + const decodedToken = await firebaseApp.auth().verifyIdToken(idToken) + // eslint-disable-next-line prefer-destructuring + userToken={ uid: decodedToken.uid, mongoID: decodedToken.mongoID } + tokenCache.set(idToken, userToken,36000); + } + + console.timeEnd("firebase"); + + if(userToken.mongoID){ + return {uid: userToken.uid, permissions: await populatePermissions(userToken.mongoID)}; } - return {uid, permissions: ["users.Auth"]}; + return {uid: userToken.uid, permissions: ["users.Auth"]}; } catch (error) { const errorMessage= error.errorInfo? error.errorInfo.message : error; diff --git a/sources/helpers/permissions.js b/sources/helpers/permissions.js index 83e2868f..4f4e1214 100644 --- a/sources/helpers/permissions.js +++ b/sources/helpers/permissions.js @@ -27,8 +27,11 @@ const rolesPermissionsMap={ * * @param {String} id Mongo User id */ -const populatePermissions = async id => { +const populatePermissions = async id => { + console.time("permission") const userAccessLevels = await AccessLevels.find({ user: id }) + + console.timeEnd("permission") if(userAccessLevels===null){ throw new Error("User not found, Possibly outdated JWT") }else{ diff --git a/sources/package.json b/sources/package.json index 78da0d03..5096a751 100644 --- a/sources/package.json +++ b/sources/package.json @@ -28,7 +28,8 @@ "firebase-admin": "^9.4.2", "graphql": "^15.4.0", "graphql-iso-date": "^3.6.1", - "mongoose": "^5.11.10" + "mongoose": "^5.11.10", + "node-cache": "^5.1.2" }, "devDependencies": { "apollo-server-testing": "^2.19.2", From 3e17054fbfc42fc53d9b5fd1d386911adc7db8e7 Mon Sep 17 00:00:00 2001 From: HarishTeens Date: Sat, 20 Mar 2021 18:19:05 +0530 Subject: [PATCH 2/2] improve performance of authFlow Make unnecessary callbacks async to give a response to the client asap. Use lean and select mongoose functions --- sources/apollo/users/user.datasources.js | 37 ++++++++++++++---------- sources/helpers/permissions.js | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/sources/apollo/users/user.datasources.js b/sources/apollo/users/user.datasources.js index 828a6ef6..a0f56058 100644 --- a/sources/apollo/users/user.datasources.js +++ b/sources/apollo/users/user.datasources.js @@ -18,6 +18,19 @@ class UserAPI extends DataSource { getUserByUsername(username) { return Users.findOne({ username }); } + async updateUserAfterSignUp(newUser,uid) { + const accessLevelObj = { + level: '1', + name:newUser.name, + user: newUser + }; + const createdAccessLevel = await AccessLevel.create(accessLevelObj); + await newUser.clubAccess.push(createdAccessLevel); + await newUser.save(); + if (process.env.NODE_ENV !== "test") { + firebase.updateJWT(uid,{mongoID:newUser._id}); + } + } /** * Single function which handles both sign up and sign in of a user * If the user already exists in the database, it simply fetches the existing doucment @@ -28,9 +41,10 @@ class UserAPI extends DataSource { * @param {String} uid firebase uid * @returns {Object} created user object */ - async authUser(user,uid) { + async authUser(user, uid) { + console.time("authuser"); let incomingUser; - const exisitingUser=await Users.findOne({firebaseUID:uid}); + const exisitingUser=await Users.findOne({firebaseUID:uid}).lean(); // User document exists(Sign in) if(exisitingUser){ incomingUser=exisitingUser; @@ -48,19 +62,12 @@ class UserAPI extends DataSource { emergencyContact: user.emergencyContact, displayPicture: user.displayPicture, }); - const accessLevelObj = { - level: '1', - name:newUser.name, - user: newUser, - }; - const createdAccessLevel = await AccessLevel.create(accessLevelObj); - await newUser.clubAccess.push(createdAccessLevel); - await newUser.save(); - if (process.env.NODE_ENV !== "test") { - firebase.updateJWT(uid,{mongoID:newUser._id}); - } - incomingUser= await Users.findOne({firebaseUID:uid}) + this.updateUserAfterSignUp(newUser,uid) + // incomingUser = await Users.findOne({ firebaseUID: uid }).lean() + incomingUser = newUser + // console.log(newUser, incomingUser); } + console.timeEnd("authuser"); return incomingUser; } /** @@ -112,7 +119,7 @@ class UserAPI extends DataSource { return {...INVALID_INPUT,message:"User Not Found"}; } - await Users.deleteOne({ id: foundUser._id }) + await Users.deleteOne({ _id: foundUser._id }) await AccessLevel.deleteMany({ user: foundUser._id }); if(process.env.NODE_ENV !== "test") { diff --git a/sources/helpers/permissions.js b/sources/helpers/permissions.js index 4f4e1214..5f2efdf9 100644 --- a/sources/helpers/permissions.js +++ b/sources/helpers/permissions.js @@ -29,7 +29,7 @@ const rolesPermissionsMap={ */ const populatePermissions = async id => { console.time("permission") - const userAccessLevels = await AccessLevels.find({ user: id }) + const userAccessLevels = await AccessLevels.find({ user: id }).select({club:1,level:1}).lean() console.timeEnd("permission") if(userAccessLevels===null){