feat: Implement authentication with JWT

This commit is contained in:
Faruk AYDIN
2022-03-05 19:58:52 +03:00
committed by Ömer Faruk Aydın
parent f883dd1287
commit c935f3f691
9 changed files with 199 additions and 31 deletions

View File

@@ -10,5 +10,6 @@ POSTGRES_USERNAME=automatish_development_user
POSTGRES_PASSWORD=
POSTGRES_ENABLE_SSL=false
ENCRYPTION_KEY=sample-encryption-key
APP_SECRET_KEY=sample-app-secret-key
REDIS_PORT=6379
REDIS_HOST=127.0.0.1

View File

@@ -32,8 +32,11 @@
"express-graphql": "^0.12.0",
"flickr-sdk": "3.10.0",
"googleapis": "89.0.0",
"graphql-middleware": "^6.1.15",
"graphql-shield": "^7.5.0",
"graphql-type-json": "^0.3.2",
"http-errors": "~1.6.3",
"jsonwebtoken": "^8.5.1",
"knex": "^0.95.11",
"lodash.get": "^4.4.2",
"morgan": "^1.10.0",
@@ -74,6 +77,7 @@
"@types/crypto-js": "^4.0.2",
"@types/express": "^4.17.13",
"@types/http-errors": "^1.8.1",
"@types/jsonwebtoken": "^8.5.8",
"@types/lodash.get": "^4.4.6",
"@types/morgan": "^1.9.3",
"@types/node": "^16.10.2",

View File

@@ -10,7 +10,6 @@ import appAssetsHandler from './helpers/app-assets-handler';
import webUIHandler from './helpers/web-ui-handler';
import errorHandler from './helpers/error-handler';
import './config/database';
import authentication from './helpers/authentication';
const app = express();
const port = appConfig.port;
@@ -21,7 +20,6 @@ app.use(morgan);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cors(corsOptions));
app.use(authentication);
app.use('/graphql', graphQLInstance);
webUIHandler(app);

View File

@@ -15,6 +15,7 @@ type AppConfig = {
postgresEnableSsl: boolean;
baseUrl?: string;
encryptionKey: string;
appSecretKey: string;
serveWebAppSeparately: boolean;
redisHost: string;
redisPort: number;
@@ -33,6 +34,7 @@ const appConfig: AppConfig = {
postgresPassword: process.env.POSTGRES_PASSWORD,
postgresEnableSsl: process.env.POSTGRES_ENABLE_SSL === 'true' ? true : false,
encryptionKey: process.env.ENCRYPTION_KEY,
appSecretKey: process.env.APP_SECRET_KEY,
serveWebAppSeparately:
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false,
redisHost: process.env.REDIS_HOST || '127.0.0.1',

View File

@@ -1,30 +1,35 @@
import { GraphQLString, GraphQLNonNull } from 'graphql';
import User from '../../models/user';
import userType from '../types/user';
import authType from '../types/auth';
import jwt from 'jsonwebtoken';
import appConfig from '../../config/app';
type Params = {
email: string,
password: string
}
email: string;
password: string;
};
const loginResolver = async (params: Params) => {
const user = await User.query().findOne({
email: params.email,
});
if (user && await user.login(params.password)) {
return user;
if (user && (await user.login(params.password))) {
const token = jwt.sign({ userId: user.id }, appConfig.appSecretKey);
return { token, user };
}
throw new Error('User could not be found.')
}
throw new Error('User could not be found.');
};
const login = {
type: userType,
type: authType,
args: {
email: { type: GraphQLNonNull(GraphQLString) },
password: { type: GraphQLNonNull(GraphQLString) }
password: { type: GraphQLNonNull(GraphQLString) },
},
resolve: (_: any, params: any) => loginResolver(params)
resolve: (_: any, params: any) => loginResolver(params),
};
export default login;

View File

@@ -0,0 +1,12 @@
import { GraphQLObjectType, GraphQLString } from 'graphql';
import UserType from './user';
const authType = new GraphQLObjectType({
name: 'Auth',
fields: {
user: { type: UserType },
token: { type: GraphQLString },
},
});
export default authType;

View File

@@ -1,14 +1,33 @@
import { Response, NextFunction } from 'express';
import { rule, shield, allow } from 'graphql-shield';
import jwt from 'jsonwebtoken';
import User from '../models/user';
import RequestWithCurrentUser from '../types/express/request-with-current-user';
import appConfig from '../config/app';
const authentication = async (req: RequestWithCurrentUser, _res: Response, next: NextFunction): Promise<void> => {
// We set authentication to use the sample user we created temporarily.
req.currentUser = await User.query().findOne({
email: 'user@automatisch.com'
}).throwIfNotFound();
const isAuthenticated = rule()(async (_parent, _args, req) => {
const token = req.headers['authorization'];
next()
}
if (token == null) return false;
try {
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
userId: string;
};
req.currentUser = await User.query().findById(userId).throwIfNotFound();
return true;
} catch (error) {
return false;
}
});
const authentication = shield({
Query: {
'*': isAuthenticated,
},
Mutation: {
'*': isAuthenticated,
login: allow,
},
});
export default authentication;

View File

@@ -1,18 +1,20 @@
import { graphqlHTTP } from 'express-graphql';
import graphQLSchema from '../graphql/graphql-schema'
import graphQLSchema from '../graphql/graphql-schema';
import logger from '../helpers/logger';
import { applyMiddleware } from 'graphql-middleware';
import authentication from '../helpers/authentication';
const graphQLInstance = graphqlHTTP({
schema: graphQLSchema,
schema: applyMiddleware(graphQLSchema, authentication),
graphiql: true,
customFormatErrorFn: (error) => {
logger.error(error.path + ' : ' + error.message + '\n' + error.stack)
logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
return {
message: error.message,
locations: error.locations
}
}
})
locations: error.locations,
};
},
});
export default graphQLInstance;

129
yarn.lock
View File

@@ -1363,6 +1363,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.5":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.7", "@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@@ -2087,6 +2094,54 @@
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210"
integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==
"@graphql-tools/batch-execute@^8.3.2":
version "8.3.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-8.3.2.tgz#8b5a731d5343f0147734f12d480aafde2a1b6eba"
integrity sha512-ICWqM+MvEkIPHm18Q0cmkvm134zeQMomBKmTRxyxMNhL/ouz6Nqld52/brSlaHnzA3fczupeRJzZ0YatruGBcQ==
dependencies:
"@graphql-tools/utils" "^8.6.2"
dataloader "2.0.0"
tslib "~2.3.0"
value-or-promise "1.0.11"
"@graphql-tools/delegate@^8.5.1":
version "8.5.1"
resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-8.5.1.tgz#3d146cc3bb74935116d3f4bddb3affdf14a9712d"
integrity sha512-/YPmVxitt57F8sH50pnfXASzOOjEfaUDkX48eF5q6f16+JBncej2zeu+Zm2c68q8MbIxhPlEGfpd0QZeqTvAxw==
dependencies:
"@graphql-tools/batch-execute" "^8.3.2"
"@graphql-tools/schema" "^8.3.2"
"@graphql-tools/utils" "^8.6.2"
dataloader "2.0.0"
graphql-executor "0.0.18"
tslib "~2.3.0"
value-or-promise "1.0.11"
"@graphql-tools/merge@^8.2.3":
version "8.2.3"
resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.2.3.tgz#a2861fec230ee7be9dc42d72fed2ac075c31669f"
integrity sha512-XCSmL6/Xg8259OTWNp69B57CPWiVL69kB7pposFrufG/zaAlI9BS68dgzrxmmSqZV5ZHU4r/6Tbf6fwnEJGiSw==
dependencies:
"@graphql-tools/utils" "^8.6.2"
tslib "~2.3.0"
"@graphql-tools/schema@^8.3.2":
version "8.3.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.3.2.tgz#5b949d7a2cc3936f73507d91cc609996f1266d11"
integrity sha512-77feSmIuHdoxMXRbRyxE8rEziKesd/AcqKV6fmxe7Zt+PgIQITxNDew2XJJg7qFTMNM43W77Ia6njUSBxNOkwg==
dependencies:
"@graphql-tools/merge" "^8.2.3"
"@graphql-tools/utils" "^8.6.2"
tslib "~2.3.0"
value-or-promise "1.0.11"
"@graphql-tools/utils@^8.6.2":
version "8.6.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.6.2.tgz#095408135f091aac68fe18a0a21b708e685500da"
integrity sha512-x1DG0cJgpJtImUlNE780B/dfp8pxvVxOD6UeykFH5rHes26S4kGokbgU8F1IgrJ1vAPm/OVBHtd2kicTsPfwdA==
dependencies:
tslib "~2.3.0"
"@graphql-typed-document-node/core@^3.0.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052"
@@ -4166,6 +4221,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/jsonwebtoken@^8.5.8":
version "8.5.8"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz#01b39711eb844777b7af1d1f2b4cf22fda1c0c44"
integrity sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==
dependencies:
"@types/node" "*"
"@types/lodash.get@^4.4.6":
version "4.4.6"
resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240"
@@ -4451,6 +4513,11 @@
dependencies:
"@types/yargs-parser" "*"
"@types/yup@0.29.11":
version "0.29.11"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e"
integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g==
"@typescript-eslint/eslint-plugin@^4.31.2":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276"
@@ -7180,6 +7247,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
dataloader@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.0.0.tgz#41eaf123db115987e21ca93c005cd7753c55fe6f"
integrity sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==
date-fns@^2.16.1:
version "2.28.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
@@ -9442,6 +9514,28 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.5
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
graphql-executor@0.0.18:
version "0.0.18"
resolved "https://registry.yarnpkg.com/graphql-executor/-/graphql-executor-0.0.18.tgz#6aa4b39e1ca773e159c2a602621e90606df0109a"
integrity sha512-upUSl7tfZCZ5dWG1XkOvpG70Yk3duZKcCoi/uJso4WxJVT6KIrcK4nZ4+2X/hzx46pL8wAukgYHY6iNmocRN+g==
graphql-middleware@^6.1.15:
version "6.1.15"
resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-6.1.15.tgz#d59ccb6e21db5d1e22a8a00d332f277794990280"
integrity sha512-JiLuIM48EE3QLcr79K0VCCHqMt6c23esLlkZv2Nr9a/yHnv6eU9DKV9eXARl+wV9m4LkT9ZCg4cIamIa9vPidQ==
dependencies:
"@graphql-tools/delegate" "^8.5.1"
"@graphql-tools/schema" "^8.3.2"
graphql-shield@^7.5.0:
version "7.5.0"
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.5.0.tgz#aa3af226946946dfadac33eccc6cbe7fec6e9000"
integrity sha512-T1A6OreOe/dHDk/1Qg3AHCrKLmTkDJ3fPFGYpSOmUbYXyDnjubK4J5ab5FjHdKHK5fWQRZNTvA0SrBObYsyfaw==
dependencies:
"@types/yup" "0.29.11"
object-hash "^2.0.3"
yup "^0.31.0"
graphql-tag@^2.12.3:
version "2.12.6"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
@@ -11655,6 +11749,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash-es@^4.17.11:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -12941,7 +13040,7 @@ object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-hash@^2.2.0:
object-hash@^2.0.3, object-hash@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
@@ -14562,6 +14661,11 @@ propagate@^2.0.0:
resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45"
integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==
property-expr@^2.0.4:
version "2.0.5"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4"
integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==
property-information@^5.0.0, property-information@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
@@ -17190,6 +17294,11 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
totalist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
@@ -17333,7 +17442,7 @@ tslib@^1.14.1, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1:
tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
@@ -17898,6 +18007,11 @@ value-equal@^1.0.1:
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
value-or-promise@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140"
integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -18822,6 +18936,17 @@ yosay@^2.0.2:
taketalk "^1.0.0"
wrap-ansi "^2.0.0"
yup@^0.31.0:
version "0.31.1"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.31.1.tgz#0954cb181161f397b804346037a04f8a4b31599e"
integrity sha512-Lf6648jDYOWR75IlWkVfwesPyW6oj+50NpxlKvsQlpPsB8eI+ndI7b4S1VrwbmeV9hIZDu1MzrlIL4W+gK1jPw==
dependencies:
"@babel/runtime" "^7.10.5"
lodash "^4.17.20"
lodash-es "^4.17.11"
property-expr "^2.0.4"
toposort "^2.0.2"
zen-observable-ts@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.3.tgz#c2f5ccebe812faf0cfcde547e6004f65b1a6d769"