From 4490041a8abc28d3ae1c03e1d2f7a3475b90996b Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Sep 2018 19:46:24 +0200 Subject: [PATCH] No more promises except the needed ones --- client/src/components/LoggedInUser.js | 4 +- client/src/components/NavBar.js | 4 +- server/app.js | 36 +++++- server/auth/TokenDecoder.js | 91 +++++++++++++ server/auth/directives.js | 69 ---------- server/schema/schema.js | 176 +++++++++++++++++++------- 6 files changed, 258 insertions(+), 122 deletions(-) create mode 100644 server/auth/TokenDecoder.js delete mode 100644 server/auth/directives.js diff --git a/client/src/components/LoggedInUser.js b/client/src/components/LoggedInUser.js index 196618322..1405419f1 100644 --- a/client/src/components/LoggedInUser.js +++ b/client/src/components/LoggedInUser.js @@ -11,8 +11,8 @@ class LoggedInUser extends Component { const user = this.props.data.userByAuthId; - console.log('loggedInUser'); - console.log(user); + console.log('loggedInUser data', this.props.data); + console.log('loggedInUser', user); if(user){ diff --git a/client/src/components/NavBar.js b/client/src/components/NavBar.js index dca0cce59..c89223a71 100644 --- a/client/src/components/NavBar.js +++ b/client/src/components/NavBar.js @@ -55,7 +55,9 @@ class NavBar extends Component { if (userStored !== null && userStored !== undefined && userStored !== 'undefined') { const user = JSON.parse(userStored); - const authId = user.authId; + const authId = user.sub; + console.log("user", user); + console.log("authid", authId); this.setState({ authId: authId }); } } diff --git a/server/app.js b/server/app.js index d97f8fc1d..f60c91372 100644 --- a/server/app.js +++ b/server/app.js @@ -1,3 +1,35 @@ +/* Debugging */ + +global.INFO_LOG = true; +global.ERROR_LOG = true; + +/* Callbacks default responses. */ + +global.DEFAULT_OK_RESPONSE = { + result: "Ok", + message: "Operation Succeeded" +}; + +global.DEFAULT_FAIL_RESPONSE = { + result: "Fail", + message: "Operation Failed" +}; + +global.DEFAULT_RETRY_RESPONSE = { + result: "Retry", + message: "Retry Later" +}; + +global.CUSTOM_OK_RESPONSE = { + result: "Ok, but check Message", + message: "Custom Message" +}; + +global.CUSTOM_FAIL_RESPONSE = { + result: "Fail Because", + message: "Custom Message" +}; + const express = require('express'); const graphqlHTTP = require('express-graphql'); const schema = require('./schema/schema'); @@ -19,6 +51,8 @@ mongoose.connection.once('open', () => { console.log('Connected to database'); }) +/* Here we bind all requests to this endpoint to be procecced by the GraphQL Library. */ + app.get('/graphql', graphqlHTTP({ schema, graphiql: true @@ -39,8 +73,6 @@ app.post('/graphql', checkJwt, (err, req, res, next) => { app.post('/graphql', (req, res, done) => getUser(req, res, done)); - - app.listen(4000,() => { console.log('Now listening for requests on port 4000'); }); diff --git a/server/auth/TokenDecoder.js b/server/auth/TokenDecoder.js new file mode 100644 index 000000000..870d3d555 --- /dev/null +++ b/server/auth/TokenDecoder.js @@ -0,0 +1,91 @@ +const MODULE_NAME = "TokenDecoder"; + +/* + JSON Web Token (JWT) is a compact, URL-safe means of representing + claims to be transferred between two parties. The claims in a JWT + are encoded as a JSON object that is used as the payload of a JSON + Web Signature (JWS) structure or as the plaintext of a JSON Web + Encryption (JWE) structure, enabling the claims to be digitally + signed or integrity protected with a Message Authentication Code + (MAC) and/or encrypted. +*/ + +const JSONWebToken = require('jsonwebtoken'); // A library to deal with JSON Web Tokens. +const webTokenLibrary = require('jwks-rsa'); // A library to retrieve RSA signing keys from a JWKS (JSON Web Key Set) endpoint. + +const AUTH_CONFIG = require('./Auth0'); // Config where we have our secrets. + +const decoder = webTokenLibrary({ + cache: true, + rateLimit: true, + jwksRequestsPerMinute: 1, + jwksUri: `https://${AUTH_CONFIG.domain}/.well-known/jwks.json` +}) + +function decodeToken (token, callBackFunction) { + + try{ + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> decodeToken -> Entering function."); } + + const decoded = JSONWebToken.decode(token, { complete: true }); + const header = decoded.header; + const payload = decoded.payload; + + if ( + header === null || + header.kid === null || + payload === null + ) { + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> Invalid Token. "); } + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> token = " + token); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + decoder.getSigningKey(header.kid, onKeyReady); + + function onKeyReady(err, key) { + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> Entering function."); } + + if (err) { + + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> Error getting the key. "); } + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> key = " + key); } + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> err = " + err); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + JSONWebToken.verify( + token, + key.publicKey, + { algorithms: ['RS256'] }, + onVerified); + + function onVerified(err, decoded) { + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> onVerified -> Entering function."); } + + if (err) { + + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> onVerified -> Error verifying. "); } + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> onVerified -> decoded = " + decoded); } + if (global.ERROR_LOG === true) { console.log ("[ERROR] " + MODULE_NAME + " -> decodeToken -> onKeyReady -> onVerified -> err = " + err); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + callBackFunction(global.DEFAULT_OK_RESPONSE, decoded); + } + } + } catch(err) { + + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> decodeToken -> err = " + err); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + + } +} + +module.exports = decodeToken; diff --git a/server/auth/directives.js b/server/auth/directives.js deleted file mode 100644 index 8111ab2cd..000000000 --- a/server/auth/directives.js +++ /dev/null @@ -1,69 +0,0 @@ -const _get = require('lodash.get') -const validateParseIdToken = require('./validate-auth0-token') -const createUser = require('./index') - -const userLocationOnContext = 'request.user' -const bearerAccessToken = 'request.headers.authorization' - -const ctxUser = ctx => _get(ctx, userLocationOnContext) -const ctxToken = ctx => _get(ctx, bearerAccessToken) - -const isLoggedIn = async ctx => { - let user = ctxUser(ctx, userLocationOnContext) - let token = ctxToken(ctx, bearerAccessToken) - let userToken - - if (!user && token) { - let scheme, credentials - const tokenParts = token.split(' ') - if (tokenParts.length === 2) { - scheme = tokenParts[0] - credentials = tokenParts[1] - } - - if (/^Bearer$/i.test(scheme)) { - token = credentials - //verify token - try { - userToken = await validateParseIdToken(token) - const auth0id = userToken.sub - - user = await ctx.db.query.user({ where: { auth0id } }) - - if (!user) { - user = await createUser(ctx, userToken) - } - } catch (err) { - userToken = false - } - } - } - - if (!user) { - throw new Error(`Not logged in`) - } - return user -} - - -const isRequestingUserAlsoOwner = ({ ctx, userId, type, typeId }) => - ctx.db.exists[type]({ id: typeId, owner: { auth0id: userId } }) -const isRequestingUser = ({ ctx, userId }) => ctx.db.exists.User({ auth0id: userId }) - -const directiveResolvers = { - isAuthenticated: async (next, source, args, ctx) => { - let result = await isLoggedIn(ctx) - console.log('directive isAuthenticated: ', result) - return next(result) - }, - hasRole: (next, source, { roles }, ctx) => { - const { role } = isLoggedIn(ctx) - console.log('directive hasRole: ', role) - if (roles.includes(role)) { - return next() - } - throw new Error(`Unauthorized, incorrect role`) - } -} - -module.exports = { directiveResolvers } diff --git a/server/schema/schema.js b/server/schema/schema.js index 104437fdc..d1c188824 100644 --- a/server/schema/schema.js +++ b/server/schema/schema.js @@ -1,8 +1,10 @@ +const MODULE_NAME = "schema"; + const graphql = require('graphql'); const _ = require('lodash'); const User = require('../models/user'); -const validateParseIdToken = require('../auth/validate-auth0-token'); +const tokenDecoder = require('../auth/TokenDecoder'); const { GraphQLObjectType, @@ -79,19 +81,59 @@ const RootQuery = new GraphQLObjectType({ resolve(parent,args) { // Code to get data from data source. + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Entering function."); } + return new Promise((resolve, reject) => { - User.findOne({authId: args.authId}, (err, user) => { - if(err) reject(err); - else{ + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> Entering function."); } - if (user.authId === args.authId && user.authId !== undefined) { - return resolve(user); - } else { - return null; + if (args.authId === null || args.authId === undefined) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> User requested not specified."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> args.authId = " + args.authId); } + + reject({ error: "No user specified" }); + return; + } + + User.findOne({authId: args.authId}, onUserReceived) + + function onUserReceived(err, user) { + if(err) { + if (ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> Database Error."); } + if (ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> err = " + err); } + reject(err); + } + else{ + + if (user === null) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> User not found at Database."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> args.authId = " + args.authId); } + + resolve({}); + return; + } + + if (user.authId === args.authId && user.authId !== undefined) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> User found at Database."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> args.authId = " + args.authId); } + + resolve(user); + return; + + } else { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> User found at Database is not the user requested."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> args.authId = " + args.authId); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserReceived -> user.authId = " + user.authId); } + + resolve({}); + return; + } } } - }) }) } @@ -125,51 +167,36 @@ const Mutation = new GraphQLObjectType({ name: 'Mutation', fields: { authenticate: { - type: UserType, - args: { - idToken: {type: new GraphQLNonNull(GraphQLString)} - }, - resolve(parent, { idToken }) { - let userToken = null; - - return validateParseIdToken(idToken) - .then(authProviderResponse => { - - console.log('validateParseIdToken', authProviderResponse); - - const authId = authProviderResponse.sub; - const alias = authProviderResponse.nickname; - const email = authProviderResponse.email; - const emailVerified = authProviderResponse.email_verified; - - let user = new User({ - alias: authId, - authId: alias, - email: email, - emailVerified: emailVerified, - roleId: "1" - }); - - user.save(); - return { authId, alias }; - }) - .catch(err => { return {error: err } }) - } - }, - addUser:{ type: UserType, args: { - alias: {type: new GraphQLNonNull(GraphQLString)}, - authId: {type: new GraphQLNonNull(GraphQLString)} + idToken: {type: new GraphQLNonNull(GraphQLString)} }, resolve(parent, args) { - let user = new User({ - alias: args.alias, - authId: args.authId, - roleId: "1" + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> Mutation -> authenticate -> resolve -> Entering function."); } + + /* In order to be able to wait for asyc calls to the database, and authorization authority, we need to return a promise to GraphQL. */ + + const promiseToGraphQL = new Promise((resolve, reject) => { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> Mutation -> authenticate -> resolve -> Promise -> Entering function."); } + authenticate(args.idToken, onAuthenticated); + + function onAuthenticated(err, responseToGraphQL) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> Mutation -> authenticate -> resolve -> Promise -> onAuthenticated -> Entering function."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> Mutation -> authenticate -> resolve -> Promise -> onAuthenticated -> responseToGraphQL = " + JSON.stringify(responseToGraphQL)); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> authenticate -> onValidated -> err.message = " + err.message); } + reject (responseToGraphQL); + } else { + resolve (responseToGraphQL); + } + } }); - return user.save(); + + return promiseToGraphQL; } }, updateUser:{ @@ -210,6 +237,59 @@ const Mutation = new GraphQLObjectType({ } }) +function authenticate(encodedToken, callBackFunction) { + +try { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> Entering function."); } + + let authId = ""; + let alias = ""; + let email = ""; + let emailVerified = false; + + tokenDecoder(encodedToken, onValidated); + + function onValidated(err, decodedToken) { + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> authenticate -> onValidated -> err.message = " + err.message); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + return; + } + + authId = decodedToken.sub; + alias = decodedToken.nickname; + email = decodedToken.email; + emailVerified = decodedToken.email_verified; + + /* The authenticated user is NOT at our module database. We need to add him. */ + + let newUser = new User({ + alias: alias, + authId: authId, + email: email, + emailVerified: emailVerified, + roleId: "1" + }); + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> " + alias + " being added to the database"); } + + newUser.save(); + + callBackFunction(global.DEFAULT_OK_RESPONSE, { authId, alias }); + + } + } catch(err) { + + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> authenticate -> err.message = " + err.message); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + + } +} + module.exports = new GraphQLSchema({ query: RootQuery, mutation: Mutation