diff --git a/README.md b/README.md index da26c4ad5..a8790edfb 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ App.js | | | | | |- UserTabs | | | | -| | | |- Tab 1 - AddUser +| | | |- Tab 1 - UserUpdate | | | | -| | | |- Tab 2 - UpdateUser +| | | |- Tab 2 - ManageImages | | | |- Browse | | | diff --git a/client/src/App.js b/client/src/App.js index 0d9c6428a..03c3cd676 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -24,7 +24,7 @@ import Post from './components/Post'; import Footer from './components/Footer'; import Callback from './components/Callback'; -import Auth from './auth/index'; +import Auth from './auth/Auth'; // Apollo Client Setup const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql', changeOrigin: true }) diff --git a/client/src/auth/index.js b/client/src/auth/Auth.js similarity index 98% rename from client/src/auth/index.js rename to client/src/auth/Auth.js index e7d84336c..b221ae134 100644 --- a/client/src/auth/index.js +++ b/client/src/auth/Auth.js @@ -8,8 +8,6 @@ import { client } from '../App' import { AUTH_CONFIG } from './Auth0' // create by renaming Auth0.sample.js to Auth0.js and setting vars -console.log(process.env.AUTH0_CLIENTID) - const AUTHENTICATE = gql` mutation authenticate($idToken: String!) { authenticate(idToken: $idToken) { @@ -128,7 +126,7 @@ class Auth { }) console.log('signinOrCreateAccount auth: ', await data) - setItem('user', data.data.authenticate.authId) + setItem('user', JSON.stringify(data.data.authenticate)) if (window.location.href.includes(`callback`)) { window.location.href = '/' } else { diff --git a/client/src/components/About.js b/client/src/components/About.js index 5283ad55d..34b02a317 100644 --- a/client/src/components/About.js +++ b/client/src/components/About.js @@ -3,9 +3,9 @@ import React from 'react' const About = () => { return (
-
-

About

-

Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae repudiandae repellat illo magni eligendi cupiditate voluptates eius nam voluptate. Incidunt nihil ullam quae quia officia quaerat, deserunt eligendi explicabo totam?

+
+

About

+

This Module was developed by the AATeam. Contributions are welcomed.

) diff --git a/client/src/components/AddUser.js b/client/src/components/AddUser.js deleted file mode 100644 index 5d40d75c8..000000000 --- a/client/src/components/AddUser.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Component } from 'react'; -import {graphql, compose} from 'react-apollo'; -import {getRolesQuery, addUserMutation, getUsersQuery} from '../queries/queries'; - -import AddUserNextButton from './AddUserNextButton'; - -class AddUser extends Component { - constructor(props){ - super(props); - this.state = { - alias: '', - authId: '1' - }; - } - - submitForm(e){ - - e.preventDefault(); - this.props.addUserMutation({ - variables: { - alias: this.state.alias, - authId: this.state.authId - }, - refetchQueries: [{ query: getUsersQuery}] // This allow us to re run whatever queries are necesary after the mutation. - }); - this.props.onAdded(); - } - - render(){ - return( -
-
-

Choose an Alias that will identify you anonimously across the whole Advanced Algos ecosystem. Choose it carefully, since the the Alias can not be changed later.

-
-
- this.setState({ alias:e.target.value})}/> - -
-
-
- - -
-
-
- ); - } -} - -export default compose( - graphql(getRolesQuery, {name: "getRolesQuery"}), - graphql(addUserMutation, {name: "addUserMutation"}) -) (AddUser); // This technique binds more than one query to a single component. diff --git a/client/src/components/Footer.js b/client/src/components/Footer.js index df47f5326..5a2b475d7 100644 --- a/client/src/components/Footer.js +++ b/client/src/components/Footer.js @@ -14,7 +14,6 @@ const Footer = () => {
Links
-
-
- -
-
-

Title 2

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum at lacus congue, suscipit elit nec, tincidunt orci.

-
-
-
-
- -
-
-

Title 3

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum at lacus congue, suscipit elit nec, tincidunt orci.

-
-
diff --git a/client/src/components/LoggedInUser.js b/client/src/components/LoggedInUser.js index 71c3e6ab4..7b6137d6d 100644 --- a/client/src/components/LoggedInUser.js +++ b/client/src/components/LoggedInUser.js @@ -13,10 +13,6 @@ class LoggedInUser extends Component { if(user){ - console.log(user.firstName); - console.log(user.lastName ); - console.log(user.alias); - let displayName = "No Display Name"; if ( @@ -38,7 +34,7 @@ class LoggedInUser extends Component { return(
-

+

); diff --git a/client/src/components/LoggedInUserMenu.js b/client/src/components/LoggedInUserMenu.js index 803dde8de..248d6ec86 100644 --- a/client/src/components/LoggedInUserMenu.js +++ b/client/src/components/LoggedInUserMenu.js @@ -19,11 +19,16 @@ class LoggedInUserMenu extends Component { constructor(props){ super(props); this.state = { - property: 'value' + user: {} } } + componentDidMount(){ + this.setState({user: this.props.user}); + } + render() { + return ( {({ anchorEl, updateAnchorEl }) => { diff --git a/client/src/components/Logout.js b/client/src/components/Logout.js index 4876ab330..0989985f7 100644 --- a/client/src/components/Logout.js +++ b/client/src/components/Logout.js @@ -1,9 +1,11 @@ import React, { Component } from 'react'; import {Redirect} from 'react-router'; +import {auth} from '../App'; class Logout extends Component { render() { - window.localStorage.removeItem("user"); + //window.localStorage.removeItem("user"); + auth.logout(); return } diff --git a/client/src/components/NavBar.js b/client/src/components/NavBar.js index b590ef192..8d210e23b 100644 --- a/client/src/components/NavBar.js +++ b/client/src/components/NavBar.js @@ -49,24 +49,21 @@ class NavBar extends Component { } componentDidMount() { - /* - this._asyncRequest = window.localStorage.getItem('user').then( - response => { - this._asyncRequest = null; - let user = JSON.parse(response) - if (user) { - const authId = user.sub; - this.setState({ authId: authId }); - } - } - ); - */ + const userStored = localStorage.getItem('user'); - console.log(userStored); if (userStored !== null && userStored !== undefined && userStored !== 'undefined') { const user = JSON.parse(userStored); - const authId = user.sub; + + /* For some reason, the result of GraphQL some times varies, that is why I had to do the following hack. */ + + let authId; + if (user.authId === undefined) { + authId = user.sub; + } else { + authId = user.authId; + } + this.setState({ authId: authId }); } } diff --git a/client/src/components/UserImages.js b/client/src/components/UserImages.js new file mode 100644 index 000000000..08ba7c5e3 --- /dev/null +++ b/client/src/components/UserImages.js @@ -0,0 +1,14 @@ +import React from 'react' + +const UserImages = () => { + return ( +
+
+

Images Manager

+

This functionality has not been developed yet. Please visit this section later.

+
+
+ ) +} + +export default UserImages diff --git a/client/src/components/UserTabs.js b/client/src/components/UserTabs.js index ef6989956..67055b9e1 100644 --- a/client/src/components/UserTabs.js +++ b/client/src/components/UserTabs.js @@ -4,15 +4,14 @@ import { withStyles } from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; -import AddIcon from '@material-ui/icons/Add'; import UpdateIcon from '@material-ui/icons/Create'; import ImageIcon from '@material-ui/icons/Wallpaper'; import Typography from '@material-ui/core/Typography'; // Components -import AddUser from './AddUser'; -import UpdateUser from './UpdateUser'; +import UserUpdate from './UserUpdate'; +import UserImages from './UserImages'; function TabContainer(props) { return ( @@ -36,23 +35,16 @@ const styles = theme => ({ class UserTabs extends React.Component { state = { - value: 0, - cureateDisabled: "false" + value: 0 }; handleChange = (event, value) => { this.setState({ value }); }; - updateUser = () => { - this.setState({ value: 1, cureateDisabled: "true" }); - }; - render() { const { classes } = this.props; const { value } = this.state; - const { createDisabled } = this.state; - return (
@@ -65,16 +57,12 @@ class UserTabs extends React.Component { indicatorColor="primary" textColor="primary" > - } /> - } /> } /> } /> - {value === 0 && } - {value === 1 && } - {value === 2 && Item Three} - + {value === 0 && } + {value === 1 && }
); } diff --git a/client/src/components/UpdateUser.js b/client/src/components/UserUpdate.js similarity index 78% rename from client/src/components/UpdateUser.js rename to client/src/components/UserUpdate.js index c8f430738..7e1e8a7c9 100644 --- a/client/src/components/UpdateUser.js +++ b/client/src/components/UserUpdate.js @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import {graphql, compose} from 'react-apollo'; import {getRolesQuery, updateUserMutation, getUsersQuery} from '../queries/queries'; -class UpdateUser extends Component { +class UserUpdate extends Component { constructor(props){ super(props); @@ -115,66 +115,67 @@ class UpdateUser extends Component { } render(){ + console.log("user at render", this.props); return( -
-
+
+

Complete your profile with the following optional information. Providing your real name might help other users trust you more.

-
-
- this.setState({ firstName:e.target.value})}/> +
+
+ this.setState({ firstName:e.target.value})}/>
-
-
- this.setState({ middleName:e.target.value})}/> +
+
+ this.setState({ middleName:e.target.value})}/>
-
-
- this.setState({ lastName:e.target.value})}/> +
+
+ this.setState({ lastName:e.target.value})}/>
-
-
- this.setState({ email:e.target.value})}/> +
+
+ this.setState({ email:e.target.value})}/>
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
@@ -188,4 +189,4 @@ class UpdateUser extends Component { export default compose( graphql(getRolesQuery, {name: "getRolesQuery"}), graphql(updateUserMutation, {name: "updateUserMutation"}) -) (UpdateUser); // This technique binds more than one query to a single component. +) (UserUpdate); // This technique binds more than one query to a single component. diff --git a/client/src/hoc/Rainbow.js b/client/src/hoc/Rainbow.js deleted file mode 100644 index 9af9ca298..000000000 --- a/client/src/hoc/Rainbow.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -const Rainbow = (WrappedComponent) => { - - const colours = ['red', 'pink', 'orange', 'blue', 'green', 'yellow']; - const randomColour = colours[Math.floor(Math.random() * 6)]; - const className = randomColour + '-text'; - - return (props) => ( -
- -
- ) - -} - -export default Rainbow diff --git a/client/src/index.js b/client/src/index.js index 9c47566b0..395b74997 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -2,12 +2,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; -//import {createStore} from 'redux'; -//import {Provider} from 'reaxt-redux'; -//import rootReducer from './reducers/rootReducer'; - -//const store = createStore(rootReducer); - -//ReactDOM.render(, document.getElementById('root')); ReactDOM.render(, document.getElementById('root')); diff --git a/client/src/pokeball.png b/client/src/pokeball.png deleted file mode 100644 index f5eff9cff..000000000 Binary files a/client/src/pokeball.png and /dev/null differ diff --git a/client/src/reducers/rootReducer.js b/client/src/reducers/rootReducer.js deleted file mode 100644 index 7017b8baa..000000000 --- a/client/src/reducers/rootReducer.js +++ /dev/null @@ -1,15 +0,0 @@ -const initState = { - user: {} -} - -const rootReducer = (state = initState, action) => { - - if(action.type === 'USER_LOGS_IN'){ - return { - user: action.user - } - } - return state; -} - -export default rootReducer diff --git a/server/app.js b/server/app.js index d97f8fc1d..aa7b6c3e3 100644 --- a/server/app.js +++ b/server/app.js @@ -1,3 +1,8 @@ + +/* This module imports */ + +const globals = require('./globals'); +const sessions = require('./sessions'); const express = require('express'); const graphqlHTTP = require('express-graphql'); const schema = require('./schema/schema'); @@ -19,6 +24,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 +46,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/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/auth/token-decoder.js b/server/auth/token-decoder.js new file mode 100644 index 000000000..ea10fb110 --- /dev/null +++ b/server/auth/token-decoder.js @@ -0,0 +1,91 @@ +const MODULE_NAME = "token-decoder"; + +/* + 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/globals.js b/server/globals.js new file mode 100644 index 000000000..22a40b8da --- /dev/null +++ b/server/globals.js @@ -0,0 +1,31 @@ +/* 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" +}; diff --git a/server/schema/schema.js b/server/schema/schema.js index c7e0c2326..75a35857c 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/token-decoder'); const { GraphQLObjectType, @@ -79,20 +81,32 @@ const RootQuery = new GraphQLObjectType({ resolve(parent,args) { // Code to get data from data source. - return new Promise((resolve, reject) => { + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Entering function."); } - User.findOne({authId: args.authId}, (err, user) => { - if(err) reject(err); - else{ + /* In order to be able to wait for asyc calls to the database, we need to return a promise to GraphQL. */ - if (user.authId === args.authId && user.authId !== undefined) { - return resolve(user); + const promiseToGraphQL = new Promise((resolve, reject) => { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> Entering function."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> args.authId = " + args.authId); } + + findUserByAuthId(args.authId, onUserFound); + + function onUserFound(err, responseToGraphQL) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserFound -> Entering function."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserFound -> responseToGraphQL = " + JSON.stringify(responseToGraphQL)); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> RootQuery -> userByAuthId -> resolve -> Promise -> onUserFound -> err.message = " + err.message); } + reject (responseToGraphQL); } else { - return null; + resolve (responseToGraphQL); } } - }) - }) + }); + + return promiseToGraphQL; } }, @@ -125,45 +139,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(result => { - console.log('validateParseIdToken', result); - const authId = result.sub; - const alias = result.nickname; - - let user = new User({ - alias: alias, - authId: authId, - roleId: "1" - }); - console.log(authId, alias); - 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 + " -> Mutation -> authenticate -> resolve -> Promise -> onAuthenticated -> err.message = " + err.message); } + reject (responseToGraphQL); + } else { + resolve (responseToGraphQL); + } + } }); - return user.save(); + + return promiseToGraphQL; } }, updateUser:{ @@ -204,6 +209,204 @@ const Mutation = new GraphQLObjectType({ } }) + +function findUserByAuthId(authId, callBackFunction) { + + try { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> Entering function."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> authId = " + authId); } + + if (authId === null || authId === undefined) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> User requested not specified."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> args.authId = " + authId); } + + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: "Bad Request" }); + return; + } + + User.findOne({authId: authId}, onUserReceived) + + function onUserReceived(err, user) { + + if(err) { + if (ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> Database Error."); } + if (ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> err = " + err); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + } + else{ + + if (user === null) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> User not found at Database."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> args.authId = " + authId); } + + let customResponse = { + result: global.CUSTOM_OK_RESPONSE.result, + message: "User Not Found" + }; + + callBackFunction(customResponse, { error: customResponse.message }); + return; + } + + if (user.authId === authId && user.authId !== undefined) { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> User found at Database."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> args.authId = " + authId); } + + callBackFunction(global.DEFAULT_OK_RESPONSE, user); + return; + + } else { + + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> User found at Database is not the user requested."); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> args.authId = " + authId); } + if (INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> findUserByAuthId -> onUserReceived -> user.authId = " + user.authId); } + + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + return; + } + } + } + } catch(err) { + + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> findUserByAuthId -> err.message = " + err.message); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + + } +} + + + +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; + } + + /* + + Ok, the user was correctly authenticated. Next we need to know if this logged in user + has already been added to this module's database or not yet. + + */ + + authId = decodedToken.sub; + alias = decodedToken.nickname; + email = decodedToken.email; + emailVerified = decodedToken.email_verified; + + findUserByAuthId(authId, onUserFound); + + function onUserFound(err, user){ + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> Entering function."); } + + if (err.result === global.DEFAULT_FAIL_RESPONSE.result) { + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> err.message = " + err.message); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + return; + } + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> User already exists at database"); } + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> Existing User: " + JSON.stringify(user)); } + + /* + + Here we save the user id of the logged in user at the server Sessions map, from there it will + be used to validate any further transaction comming from this same user. + + */ + + global.Sessions.set(encodedToken, user.id); + + callBackFunction(global.DEFAULT_OK_RESPONSE, { authId: authId, alias: user.alias }); + return; + } + + if ( + err.result === global.CUSTOM_OK_RESPONSE.result && + err.message === "User Not Found" + ) { + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> User does not exist at database"); } + + /* + + The authenticated user is NOT at our module database. We need to add him. + We will take from the authentication provider the basic information it knows about the logged in users + and save it as an initial set of data, which can later be modified. + + */ + + 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 -> onUserFound -> " + alias + " being added to the database"); } + + newUser.save(onSaved); + + function onSaved(err, savedUser) { + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> onSaved -> Entering function."); } + + if (err) { + if (global.ERROR_LOG === true) { console.log("[ERROR] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> onSaved -> err = " + err); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE, { error: err }); + return; + } + + if (global.INFO_LOG === true) { console.log("[INFO] " + MODULE_NAME + " -> authenticate -> onValidated -> onUserFound -> onSaved -> Saved User: " + JSON.stringify(savedUser)); } + + /* + + Here we save the user id of the logged in user at the server Sessions map, from there it will + be used to validate any further transaction comming from this same user. + + */ + + global.Sessions.set(encodedToken, savedUser.id); + + 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 diff --git a/server/sessions.js b/server/sessions.js new file mode 100644 index 000000000..9ba7d9aef --- /dev/null +++ b/server/sessions.js @@ -0,0 +1,16 @@ +/* + +Global in-memory Sessions + +Here, we are going to store some information associated to the logged in user, after it was +succesfully authenticated. This will prevent cheating, since we will use the in-Session +user-id for any transaction belonging to the user instead of whatever user id it provides +from the client app. + +The key to access the in-Session information will allways be the authenticatioin token received +at the authentication transaction. This token should always come at the header of each +transactional functionality supported by this server module. + +*/ + +global.Sessions = new Map();