From 324518feac438d746bd950d3377551b25b18622f Mon Sep 17 00:00:00 2001 From: Michael Ossig Date: Fri, 18 Mar 2022 12:53:13 -0400 Subject: [PATCH 1/4] updated typings, refactored exports --- packages/client/src/app/actions.ts | 3 +-- packages/client/src/app/index.ts | 8 ++++---- packages/client/src/app/reducers/configuration.ts | 4 +--- packages/client/src/app/reducers/index.ts | 6 ++---- packages/client/src/app/reducers/user.ts | 4 +--- packages/client/src/{config.js => config.ts} | 0 packages/client/src/hooks/index.ts | 11 ++++++----- packages/client/src/hooks/types.ts | 4 +++- 8 files changed, 18 insertions(+), 22 deletions(-) rename packages/client/src/{config.js => config.ts} (100%) diff --git a/packages/client/src/app/actions.ts b/packages/client/src/app/actions.ts index fbd9507..fcf79c7 100644 --- a/packages/client/src/app/actions.ts +++ b/packages/client/src/app/actions.ts @@ -1,2 +1 @@ -export { updateUserInfo, clearUserInfo } from './reducers/user'; -export { updateConfigurationInfo, clearConfigurationInfo } from './reducers/configuration'; +export { userActions, configurationActions } from './reducers'; diff --git a/packages/client/src/app/index.ts b/packages/client/src/app/index.ts index 7d17111..5253f17 100644 --- a/packages/client/src/app/index.ts +++ b/packages/client/src/app/index.ts @@ -1,5 +1,5 @@ -import * as actions from './actions'; -import * as selectors from './selectors'; -import reducers from './reducers'; +export * as actions from './actions'; +export * as selectors from './selectors'; +export { userReducer, configurationReducer } from './reducers'; -export { actions, selectors, reducers }; +export type { ConfigurationState, UserState } from './types'; diff --git a/packages/client/src/app/reducers/configuration.ts b/packages/client/src/app/reducers/configuration.ts index 8493020..602b795 100644 --- a/packages/client/src/app/reducers/configuration.ts +++ b/packages/client/src/app/reducers/configuration.ts @@ -23,6 +23,4 @@ export const configurationSlice = createSlice({ } }); -export const { updateConfigurationInfo, clearConfigurationInfo } = configurationSlice.actions; - -export default configurationSlice.reducer; +export const { actions, reducer } = configurationSlice; diff --git a/packages/client/src/app/reducers/index.ts b/packages/client/src/app/reducers/index.ts index 15fceff..8d75807 100644 --- a/packages/client/src/app/reducers/index.ts +++ b/packages/client/src/app/reducers/index.ts @@ -1,4 +1,2 @@ -import userReducer from './user'; -import configurationReducer from './configuration'; - -export default { userReducer, configurationReducer }; +export { reducer as userReducer, actions as userActions } from './user'; +export { reducer as configurationReducer, actions as configurationActions } from './configuration'; diff --git a/packages/client/src/app/reducers/user.ts b/packages/client/src/app/reducers/user.ts index dbdbda6..107b99d 100644 --- a/packages/client/src/app/reducers/user.ts +++ b/packages/client/src/app/reducers/user.ts @@ -20,6 +20,4 @@ export const userSlice = createSlice({ } }); -export const { updateUserInfo, clearUserInfo } = userSlice.actions; - -export default userSlice.reducer; +export const { actions, reducer } = userSlice; diff --git a/packages/client/src/config.js b/packages/client/src/config.ts similarity index 100% rename from packages/client/src/config.js rename to packages/client/src/config.ts diff --git a/packages/client/src/hooks/index.ts b/packages/client/src/hooks/index.ts index 186bf0e..5bcf302 100644 --- a/packages/client/src/hooks/index.ts +++ b/packages/client/src/hooks/index.ts @@ -1,9 +1,10 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { useCheckout } from './checkout/useCheckout'; -import { useInitializeCheckout } from './checkout/useInitializeCheckout'; import type { RootState, AppDispatch } from '../store'; -const useAppDispatch = () => useDispatch(); -const useAppSelector: TypedUseSelectorHook = useSelector; +export type { InitializationRequest, EditableCheckoutConfigFields, CheckoutConfig, PaymentAmount, PaymentMethodsResponseInterface } from './types'; -export { useCheckout, useInitializeCheckout, useAppDispatch, useAppSelector }; +export { useCheckout } from './checkout/useCheckout'; +export { useInitializeCheckout } from './checkout/useInitializeCheckout'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/packages/client/src/hooks/types.ts b/packages/client/src/hooks/types.ts index 1df3722..6c5d0ae 100644 --- a/packages/client/src/hooks/types.ts +++ b/packages/client/src/hooks/types.ts @@ -1,4 +1,4 @@ -import { PaymentAmount, PaymentMethodsResponseInterface } from '@adyen/adyen-web/dist/types/types'; +import type { PaymentAmount, PaymentMethodsResponseInterface } from '@adyen/adyen-web/dist/types/types'; export type InitializationRequest = { merchantAccount: string; @@ -39,3 +39,5 @@ export interface CheckoutConfig extends EditableCheckoutConfigFields { onError?: (error: any, element?: any) => void; onPaymentCompleted?: (result: any, element: any) => void; } + +export { PaymentAmount, PaymentMethodsResponseInterface }; From 650ae40c3d3c408f18e9d035b351afee8298b350 Mon Sep 17 00:00:00 2001 From: Michael Ossig Date: Fri, 18 Mar 2022 13:05:12 -0400 Subject: [PATCH 2/4] refactoring imports and file structure --- packages/server/models/types.ts | 4 ++-- packages/server/routes/adyen-endpoints/sessions.ts | 6 +++--- packages/server/test/routes/helpers/index.ts | 1 + .../test/routes/{users/helpers.ts => helpers/users.ts} | 0 packages/server/test/routes/users/configurations.test.ts | 8 +++++--- packages/server/test/routes/users/users.test.ts | 8 +++++--- packages/server/tsconfig.json | 1 + 7 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 packages/server/test/routes/helpers/index.ts rename packages/server/test/routes/{users/helpers.ts => helpers/users.ts} (100%) diff --git a/packages/server/models/types.ts b/packages/server/models/types.ts index c3a7e45..1529de1 100644 --- a/packages/server/models/types.ts +++ b/packages/server/models/types.ts @@ -7,8 +7,8 @@ export interface UserDocument extends Document { password: string; email: string; adyenKey?: string; - merchantAccounts?: [string]; - configurations?: [Types.ObjectId]; + merchantAccounts?: string[]; + configurations?: Types.ObjectId[]; } export interface ConfigurationDocument extends Document { diff --git a/packages/server/routes/adyen-endpoints/sessions.ts b/packages/server/routes/adyen-endpoints/sessions.ts index 5c693c9..e3596a8 100644 --- a/packages/server/routes/adyen-endpoints/sessions.ts +++ b/packages/server/routes/adyen-endpoints/sessions.ts @@ -4,15 +4,15 @@ import request from 'request-promise'; import { errorHandler } from '../helpers'; import { ADYEN_API_KEY, ADYEN_BASE_URL } from '../../config'; -import type { InitializationRequest } from './types'; -import { CheckoutSessionSetupResponse } from '@adyen/adyen-web/dist/types/types'; +import type { InitializationRequest, RequestOptions } from './types'; +import type { CheckoutSessionSetupResponse } from '@adyen/adyen-web/dist/types/types'; const router = Router(); router.post('/sessionStart', async (req, res) => { const { version, apiKey, payload }: InitializationRequest = req.body; try { - const options = { + const options: RequestOptions = { url: `${ADYEN_BASE_URL}/${version}/sessions`, headers: { 'Content-type': 'application/json', diff --git a/packages/server/test/routes/helpers/index.ts b/packages/server/test/routes/helpers/index.ts new file mode 100644 index 0000000..9ae2b3a --- /dev/null +++ b/packages/server/test/routes/helpers/index.ts @@ -0,0 +1 @@ +export * as userHelpers from './users'; diff --git a/packages/server/test/routes/users/helpers.ts b/packages/server/test/routes/helpers/users.ts similarity index 100% rename from packages/server/test/routes/users/helpers.ts rename to packages/server/test/routes/helpers/users.ts diff --git a/packages/server/test/routes/users/configurations.test.ts b/packages/server/test/routes/users/configurations.test.ts index a7cf793..018fde4 100644 --- a/packages/server/test/routes/users/configurations.test.ts +++ b/packages/server/test/routes/users/configurations.test.ts @@ -1,15 +1,17 @@ import chai from 'chai'; import mongoose from 'mongoose'; import chaiHttp from 'chai-http'; -const { wrongAuthToken } = require('../../structures').userTestData; +import { userTestData } from '../../structures'; import { TEST_DATABASE_URL } from '../../../config'; -import { createMockConfigurations } from './helpers'; +import { userHelpers } from '../helpers'; import { app, runServer, closeServer } from '../../../index'; const assert = chai.assert; chai.use(chaiHttp); +const { createMockConfigurations } = userHelpers; + const tearDownDb = () => { return new Promise((resolve, reject) => { mongoose.connection @@ -46,7 +48,7 @@ describe('Configurations API', () => { let agent = chai.request.agent(app); return agent .get(`/configurations/${userId}/${mockConfig.body.id}`) - .set('Authorization', `Bearer ${wrongAuthToken}`) + .set('Authorization', `Bearer ${userTestData.wrongAuthToken}`) .then(res => { assert.equal(res.status, 401, 'failed status check'); return res; diff --git a/packages/server/test/routes/users/users.test.ts b/packages/server/test/routes/users/users.test.ts index 477439b..7b61d87 100644 --- a/packages/server/test/routes/users/users.test.ts +++ b/packages/server/test/routes/users/users.test.ts @@ -1,15 +1,17 @@ import chai from 'chai'; import mongoose from 'mongoose'; import chaiHttp from 'chai-http'; -const { wrongAuthToken } = require('../../structures').userTestData; +import { userTestData } from '../../structures'; import { TEST_DATABASE_URL } from '../../../config'; -import { createMockUser, logUserIn } from './helpers'; +import { userHelpers } from '../helpers'; import { app, runServer, closeServer } from '../../../index'; const assert = chai.assert; chai.use(chaiHttp); +const { createMockUser, logUserIn } = userHelpers; + const tearDownDb = () => { return new Promise((resolve, reject) => { mongoose.connection @@ -63,7 +65,7 @@ describe('Users API', () => { const authToken = await logUserIn(); const mockPayload = { id: mockUser.body.id, - adyenKey: wrongAuthToken, + adyenKey: userTestData.wrongAuthToken, merchantAccounts: ['TestMerchant1', 'TestMerchant2'] }; diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index b331986..6512d3a 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -4,6 +4,7 @@ "types": ["mocha"], "target": "es6", "rootDir": "./", + "strict": true, "esModuleInterop": true, "resolveJsonModule": true } From d870bb6600cb0f290c14b288f95175fdb7501dfa Mon Sep 17 00:00:00 2001 From: Michael Ossig Date: Fri, 18 Mar 2022 14:52:31 -0400 Subject: [PATCH 3/4] refactored and fixed typing errors --- package-lock.json | 104 ++++++++++++++++++ packages/server/config.ts | 2 +- packages/server/db-mongoose.ts | 2 +- packages/server/index.ts | 4 +- packages/server/models/index.ts | 1 + .../server/models/users/configurations.ts | 6 +- packages/server/models/users/users.ts | 4 +- packages/server/package.json | 5 + .../server/routes/adyen-endpoints/index.ts | 2 + .../server/routes/adyen-endpoints/payments.ts | 14 +-- .../server/routes/adyen-endpoints/sessions.ts | 6 +- .../server/routes/adyen-endpoints/types.ts | 2 + packages/server/routes/auth/auth.ts | 8 +- packages/server/routes/auth/middleware.ts | 15 ++- packages/server/routes/auth/strategies.ts | 2 +- .../server/routes/users/configurations.ts | 32 +++--- packages/server/routes/users/types.ts | 11 ++ packages/server/routes/users/users.ts | 26 +++-- .../routes/helpers/{users.ts => helpers.ts} | 4 +- packages/server/test/routes/helpers/index.ts | 2 +- 20 files changed, 197 insertions(+), 55 deletions(-) create mode 100644 packages/server/routes/users/types.ts rename packages/server/test/routes/helpers/{users.ts => helpers.ts} (90%) diff --git a/package-lock.json b/package-lock.json index 403aa8d..e3afd59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3640,6 +3640,12 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, "node_modules/@types/bluebird": { "version": "3.5.36", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", @@ -3822,6 +3828,15 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", + "integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -3873,6 +3888,38 @@ "@types/express": "*" } }, + "node_modules/@types/passport-jwt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.6.tgz", + "integrity": "sha512-cmAAMIRTaEwpqxlrZyiEY9kdibk94gP5KTF8AT1Ra4rWNZYHNMreqhKUEeC5WJtuN5SJZjPQmV+XO2P5PlnvNQ==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.34.tgz", + "integrity": "sha512-PSc07UdYx+jhadySxxIYWuv6sAnY5e+gesn/5lkPKfBeGuIYn9OPR+AAEDq73VRUh6NBTpvE/iPE62rzZUslog==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/prettier": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", @@ -25627,13 +25674,18 @@ "validator": "^13.7.0" }, "devDependencies": { + "@types/bcryptjs": "^2.4.2", "@types/chai": "^4.3.0", "@types/cookie-parser": "^1.4.2", "@types/expect": "^24.3.0", "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.28", + "@types/jsonwebtoken": "^8.5.8", "@types/mocha": "^9.1.0", "@types/mongoose": "^5.11.97", "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/passport-local": "^1.0.34", "@types/request-promise": "^4.1.48", "chai": "^4.3.6", "chai-http": "^4.3.0", @@ -28124,6 +28176,12 @@ "@babel/types": "^7.3.0" } }, + "@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, "@types/bluebird": { "version": "3.5.36", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", @@ -28305,6 +28363,15 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "@types/jsonwebtoken": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", + "integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -28355,6 +28422,38 @@ "@types/express": "*" } }, + "@types/passport-jwt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.6.tgz", + "integrity": "sha512-cmAAMIRTaEwpqxlrZyiEY9kdibk94gP5KTF8AT1Ra4rWNZYHNMreqhKUEeC5WJtuN5SJZjPQmV+XO2P5PlnvNQ==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-local": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.34.tgz", + "integrity": "sha512-PSc07UdYx+jhadySxxIYWuv6sAnY5e+gesn/5lkPKfBeGuIYn9OPR+AAEDq73VRUh6NBTpvE/iPE62rzZUslog==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, "@types/prettier": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", @@ -28981,13 +29080,18 @@ "adyen-demo-server": { "version": "file:packages/server", "requires": { + "@types/bcryptjs": "*", "@types/chai": "^4.3.0", "@types/cookie-parser": "^1.4.2", "@types/expect": "^24.3.0", "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.28", + "@types/jsonwebtoken": "^8.5.8", "@types/mocha": "^9.1.0", "@types/mongoose": "^5.11.97", "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/passport-local": "^1.0.34", "@types/request-promise": "^4.1.48", "@types/validator": "^13.7.1", "bcrypt": "^5.0.1", diff --git a/packages/server/config.ts b/packages/server/config.ts index 809fb6d..cd39a82 100644 --- a/packages/server/config.ts +++ b/packages/server/config.ts @@ -1,5 +1,5 @@ export const PORT = process.env.PORT || 8080; -export const ADYEN_API_KEY = process.env.ADYEN_API_KEY || null; +export const ADYEN_API_KEY = process.env.ADYEN_API_KEY || ''; export const ADYEN_BASE_URL = process.env.ADYEN_BASE_URL || 'https://checkout-test.adyen.com'; export const DATABASE_URL = process.env.DATABASE_URL || 'mongodb://localhost/adyen-demo'; export const TEST_DATABASE_URL = process.env.TEST_DATABASE_URL || 'mongodb://localhost/adyen-demo-test'; diff --git a/packages/server/db-mongoose.ts b/packages/server/db-mongoose.ts index f145cdb..203605a 100644 --- a/packages/server/db-mongoose.ts +++ b/packages/server/db-mongoose.ts @@ -7,7 +7,7 @@ export const mongoOptions = { useUnifiedTopology: true }; -export const dbConnect = url => { +export const dbConnect = (url: string) => { return mongoose.connect(url, mongoOptions as ConnectOptions).catch(err => { console.error('Mongoose failed to connect'); console.error(err); diff --git a/packages/server/index.ts b/packages/server/index.ts index df6ff2a..93eb1bb 100644 --- a/packages/server/index.ts +++ b/packages/server/index.ts @@ -34,7 +34,7 @@ app.use('/sessions', sessionsRouter); app.use('/payments', paymentsRouter); app.use('/configurations', configurationRouter); -let server; +let server: any; export const runServer = (databaseUrl = DATABASE_URL, port = PORT) => { return new Promise((resolve, reject) => { @@ -59,7 +59,7 @@ export const closeServer = () => { return mongoose.disconnect().then(() => { return new Promise((resolve, reject) => { console.log('Closing server'); - return server.close(err => { + return server.close((err: any) => { if (err) { return reject(err); } diff --git a/packages/server/models/index.ts b/packages/server/models/index.ts index c072069..36f0487 100644 --- a/packages/server/models/index.ts +++ b/packages/server/models/index.ts @@ -1 +1,2 @@ export { User, Configuration } from './users'; +export type { UserDocument, ConfigurationDocument } from './types'; diff --git a/packages/server/models/users/configurations.ts b/packages/server/models/users/configurations.ts index 2769f85..10cd206 100644 --- a/packages/server/models/users/configurations.ts +++ b/packages/server/models/users/configurations.ts @@ -1,4 +1,4 @@ -import { Schema, SchemaTypes, model } from 'mongoose'; +import { Schema, SchemaTypes, Model, model } from 'mongoose'; import { ConfigurationDocument } from '../types'; @@ -6,6 +6,8 @@ export interface Configuration extends ConfigurationDocument { apiRepr(): ConfigurationDocument; } +export interface ConfigurationModel extends Model {} + export const ConfigurationSchema: Schema = new Schema({ owner: { type: SchemaTypes.ObjectId, ref: 'User', required: true }, name: { type: String, required: true }, @@ -25,6 +27,6 @@ ConfigurationSchema.method('apiRepr', function () { }; }); -export const Configuration = model('Configuration', ConfigurationSchema); +export const Configuration: ConfigurationModel = model('Configuration', ConfigurationSchema); export default Configuration; diff --git a/packages/server/models/users/users.ts b/packages/server/models/users/users.ts index f76b602..7f980d8 100644 --- a/packages/server/models/users/users.ts +++ b/packages/server/models/users/users.ts @@ -50,11 +50,11 @@ UserSchema.method('apiRepr', function () { }; }); -UserSchema.method('validatePassword', function (password: string): boolean { +UserSchema.method('validatePassword', function (password: string): Promise { return compare(password, this.password); }); -UserSchema.static('hashPassword', (password: string): string => hash(password, 10)); +UserSchema.static('hashPassword', (password: string): Promise => hash(password, 10)); export const User: UserModel = model('User', UserSchema); diff --git a/packages/server/package.json b/packages/server/package.json index 9a805d3..ddbff19 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -28,13 +28,18 @@ "validator": "^13.7.0" }, "devDependencies": { + "@types/bcryptjs": "^2.4.2", "@types/chai": "^4.3.0", "@types/cookie-parser": "^1.4.2", "@types/expect": "^24.3.0", "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.28", + "@types/jsonwebtoken": "^8.5.8", "@types/mocha": "^9.1.0", "@types/mongoose": "^5.11.97", "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/passport-local": "^1.0.34", "@types/request-promise": "^4.1.48", "chai": "^4.3.6", "chai-http": "^4.3.0", diff --git a/packages/server/routes/adyen-endpoints/index.ts b/packages/server/routes/adyen-endpoints/index.ts index 8d9906b..32825b5 100644 --- a/packages/server/routes/adyen-endpoints/index.ts +++ b/packages/server/routes/adyen-endpoints/index.ts @@ -1,2 +1,4 @@ export { router as sessionsRouter } from './sessions'; export { router as paymentsRouter } from './payments'; + +export type { BaseAdyenRequest, InitializationRequest, RequestOptions, PaymentAmount } from './types'; diff --git a/packages/server/routes/adyen-endpoints/payments.ts b/packages/server/routes/adyen-endpoints/payments.ts index 5127887..ae25f88 100644 --- a/packages/server/routes/adyen-endpoints/payments.ts +++ b/packages/server/routes/adyen-endpoints/payments.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Request, Response, Router } from 'express'; import request from 'request-promise'; import { errorHandler } from '../helpers'; import { ADYEN_API_KEY, ADYEN_BASE_URL } from '../../config'; @@ -8,7 +8,7 @@ import type { PaymentMethodsResponseInterface } from '@adyen/adyen-web/dist/type const router = Router(); -router.post('/getPaymentMethods', async (req, res) => { +router.post('/getPaymentMethods', async (req: Request, res: Response) => { const { version, apiKey, payload }: InitializationRequest = req.body; try { const options: RequestOptions = { @@ -23,12 +23,12 @@ router.post('/getPaymentMethods', async (req, res) => { const response: PaymentMethodsResponseInterface = await request(options); res.send(201).json(response); - } catch (err) { + } catch (err: any) { errorHandler('/getPaymentMethods', 500, err.message, res); } }); -router.post('/makePayment', async (req, res) => { +router.post('/makePayment', async (req: Request, res: Response) => { const { version, apiKey, payload } = req.body; try { const options: RequestOptions = { @@ -43,12 +43,12 @@ router.post('/makePayment', async (req, res) => { const response = await request(options); res.send(201).json(response); - } catch (err) { + } catch (err: any) { errorHandler('/makePayment', 500, err.message, res); } }); -router.post('/additionalDetails', async (req, res) => { +router.post('/additionalDetails', async (req: Request, res: Response) => { const { version, apiKey, payload } = req.body; try { const options: RequestOptions = { @@ -63,7 +63,7 @@ router.post('/additionalDetails', async (req, res) => { const response = await request(options); res.send(201).json(response); - } catch (err) { + } catch (err: any) { errorHandler('/additionalDetails', 500, err.message, res); } }); diff --git a/packages/server/routes/adyen-endpoints/sessions.ts b/packages/server/routes/adyen-endpoints/sessions.ts index e3596a8..860fb75 100644 --- a/packages/server/routes/adyen-endpoints/sessions.ts +++ b/packages/server/routes/adyen-endpoints/sessions.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Router, Request, Response } from 'express'; import request from 'request-promise'; import { errorHandler } from '../helpers'; @@ -9,7 +9,7 @@ import type { CheckoutSessionSetupResponse } from '@adyen/adyen-web/dist/types/t const router = Router(); -router.post('/sessionStart', async (req, res) => { +router.post('/sessionStart', async (req: Request, res: Response) => { const { version, apiKey, payload }: InitializationRequest = req.body; try { const options: RequestOptions = { @@ -24,7 +24,7 @@ router.post('/sessionStart', async (req, res) => { const response: CheckoutSessionSetupResponse = await request(options); res.send(201).json(response); - } catch (err) { + } catch (err: any) { errorHandler('/sessionStart', 500, err.message, res); } }); diff --git a/packages/server/routes/adyen-endpoints/types.ts b/packages/server/routes/adyen-endpoints/types.ts index 5c5e4b1..8cccdeb 100644 --- a/packages/server/routes/adyen-endpoints/types.ts +++ b/packages/server/routes/adyen-endpoints/types.ts @@ -29,3 +29,5 @@ export interface RequestOptions { body: any; json: boolean; } + +export type { PaymentAmount }; diff --git a/packages/server/routes/auth/auth.ts b/packages/server/routes/auth/auth.ts index 22d1b64..a1ef7c8 100644 --- a/packages/server/routes/auth/auth.ts +++ b/packages/server/routes/auth/auth.ts @@ -1,11 +1,11 @@ import jwt from 'jsonwebtoken'; -import express from 'express'; import passport from 'passport'; +import express, { Request, Response } from 'express'; import { JWT_EXPIRY, JWT_SECRET } from '../../config'; const router = express.Router(); -const createAuthToken = (user: any): object => { +const createAuthToken = (user: any): string => { return jwt.sign({ user }, JWT_SECRET, { subject: user.username, expiresIn: JWT_EXPIRY, @@ -15,14 +15,14 @@ const createAuthToken = (user: any): object => { const localAuth = passport.authenticate('local', { session: false }); -router.post('/login', localAuth, (req, res) => { +router.post('/login', localAuth, (req: Request, res: Response) => { const authToken = createAuthToken(req.user); res.json({ authToken }); }); const jwtAuth = passport.authenticate('jwt', { session: false }); -router.post('/refresh', jwtAuth, (req, res) => { +router.post('/refresh', jwtAuth, (req: Request, res: Response) => { const authToken = createAuthToken(req.user); res.json({ authToken }); }); diff --git a/packages/server/routes/auth/middleware.ts b/packages/server/routes/auth/middleware.ts index 07436fe..920bd71 100644 --- a/packages/server/routes/auth/middleware.ts +++ b/packages/server/routes/auth/middleware.ts @@ -1,5 +1,6 @@ +import { Request, Response, NextFunction } from 'express'; +import { ParamsDictionary } from 'express-serve-static-core'; import jwt_decode from 'jwt-decode'; -import { Types } from 'mongoose'; type TokenData = { user: { @@ -12,8 +13,16 @@ type TokenData = { }; }; -export const isAuthorizedForAction = (req, res, next) => { - const userToken: string = req.headers.authorization.split(' ')[1]; +type AuthorizedParams = { + userId: string; +}; + +interface AuthorizationRequest extends Request { + params: T; +} + +export const isAuthorizedForAction = (req: AuthorizationRequest, res: Response, next: NextFunction) => { + const userToken: string = req.headers.authorization ? req.headers.authorization.split(' ')[1] : ''; const { userId }: { userId: string } = req.params; const tokenData: TokenData = jwt_decode(userToken); return tokenData.user._id === userId diff --git a/packages/server/routes/auth/strategies.ts b/packages/server/routes/auth/strategies.ts index ae63839..ae5e086 100644 --- a/packages/server/routes/auth/strategies.ts +++ b/packages/server/routes/auth/strategies.ts @@ -25,7 +25,7 @@ export const localStrategy = new LocalStrategy(async (username, password, callba } return callback(null, user); - } catch (err) { + } catch (err: any) { if (err.reason === 'LoginError') { return callback(null, false, err); } diff --git a/packages/server/routes/users/configurations.ts b/packages/server/routes/users/configurations.ts index feb1515..b0132cd 100644 --- a/packages/server/routes/users/configurations.ts +++ b/packages/server/routes/users/configurations.ts @@ -1,11 +1,13 @@ -import { Router } from 'express'; +import { Router, Request, Response } from 'express'; -import { jwtAuth, isAuthorizedForAction } from '../auth'; import { User, Configuration } from '../../models'; +import { jwtAuth, isAuthorizedForAction } from '../auth'; + +import type { ConfigToUpdate } from './types'; const router = Router(); -router.get('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { +router.get('/:userId', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { try { const existingUser = await User.find({ _id: req.params.userId }); if (!existingUser || !existingUser.length) { @@ -28,13 +30,13 @@ router.get('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { } res.status(201).json(relatedConfigurations.map(config => config.apiRepr())); - } catch (err) { + } catch (err: any) { console.error('ERROR GETTING CONFIGS', err); res.status(500).json({ code: 500, message: 'Internal server error' }); } }); -router.get('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req, res) => { +router.get('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { try { const existingConfiguration = await Configuration.find({ _id: req.params.id }); if (!existingConfiguration || !existingConfiguration.length) { @@ -53,7 +55,7 @@ router.get('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req, res) => { } }); -router.post('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { +router.post('/:userId', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { try { const { owner, name, version, configuration } = req.body; @@ -75,15 +77,15 @@ router.post('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { } }); -router.put('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req, res) => { +router.put('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req: Request, res: Response) => { if (!(req.params.id === req.body.id)) { const message = `Request patch id (${req.params.id} and request body id (${req.body.id}) must match)`; console.error(message); res.status(400).json({ message: message }); } try { - const toUpdate = {}; - const updateableFields = ['name', 'version', 'configuration']; + const toUpdate: ConfigToUpdate = {}; + const updateableFields = ['name' as const, 'version' as const, 'configuration' as const]; updateableFields.forEach(field => { if (field in req.body) { @@ -91,12 +93,12 @@ router.put('/:userId/:id', jwtAuth, isAuthorizedForAction, async (req, res) => { } }); - const { owner, name, version, configuration } = await Configuration.findOneAndUpdate( - { _id: req.body.id }, - { $set: toUpdate }, - { new: true } - ).exec(); - res.send(200).json({ id: req.body.id, owner, name, version, configuration }); + const updatedConfig = await Configuration.findOneAndUpdate({ _id: req.body.id }, { $set: toUpdate }, { new: true }).exec(); + if (updatedConfig) { + const { owner, name, version, configuration } = updatedConfig; + return res.send(200).json({ id: req.body.id, owner, name, version, configuration }); + } + res.status(500).json({ message: 'Internal server error' }); } catch (err) { console.error('CONFIGURATIONS UPDATE ERROR', err); res.status(500).json({ message: 'Internal server error' }); diff --git a/packages/server/routes/users/types.ts b/packages/server/routes/users/types.ts new file mode 100644 index 0000000..9f2e529 --- /dev/null +++ b/packages/server/routes/users/types.ts @@ -0,0 +1,11 @@ +export type ConfigToUpdate = { + name?: string; + version?: number; + configuration?: string; +}; + +export type UserToUpdate = { + adyenKey?: string; + merchantAccounts?: string[]; + configurations?: string[]; +}; diff --git a/packages/server/routes/users/users.ts b/packages/server/routes/users/users.ts index 770fc13..ea1c97e 100644 --- a/packages/server/routes/users/users.ts +++ b/packages/server/routes/users/users.ts @@ -1,10 +1,13 @@ import { Router } from 'express'; -import { jwtAuth, isAuthorizedForAction } from '../auth'; -import { runUserValidation } from '../helpers'; - -const router = Router(); import { User } from '../../models'; +import { runUserValidation } from '../helpers'; + +import { jwtAuth, isAuthorizedForAction } from '../auth'; + +import type { UserToUpdate } from './types'; + +const router = Router(); router.post('/', async (req, res) => { try { @@ -69,8 +72,8 @@ router.put('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { } try { - const toUpdate = {}; - const updateableFields = ['adyenKey', 'merchantAccounts', 'configurations']; + const toUpdate: UserToUpdate = {}; + const updateableFields = ['adyenKey' as const, 'merchantAccounts' as const, 'configurations' as const]; updateableFields.forEach(field => { if (field in req.body) { @@ -78,13 +81,12 @@ router.put('/:userId', jwtAuth, isAuthorizedForAction, async (req, res) => { } }); - const { adyenKey, merchantAccounts, configurations } = await User.findOneAndUpdate( - { _id: req.body.id }, - { $set: toUpdate }, - { new: true } - ).exec(); + const foundUser = await User.findOneAndUpdate({ _id: req.body.id }, { $set: toUpdate }, { new: true }).exec(); - res.status(200).json({ id: req.body.id, adyenKey: adyenKey.substr(adyenKey.length - 5), merchantAccounts, configurations }); + if (foundUser) { + const { adyenKey, merchantAccounts, configurations } = foundUser; + res.status(200).json({ id: req.body.id, adyenKey: adyenKey ? adyenKey.substring(adyenKey.length - 5) : '', merchantAccounts, configurations }); + } } catch (err) { console.error('USER UPDATE ERROR', err); res.status(500).json({ message: 'Internal server error' }); diff --git a/packages/server/test/routes/helpers/users.ts b/packages/server/test/routes/helpers/helpers.ts similarity index 90% rename from packages/server/test/routes/helpers/users.ts rename to packages/server/test/routes/helpers/helpers.ts index c11d35e..846ea77 100644 --- a/packages/server/test/routes/helpers/users.ts +++ b/packages/server/test/routes/helpers/helpers.ts @@ -1,10 +1,12 @@ import chai from 'chai'; import chaiHttp from 'chai-http'; import { app } from '../../../index'; -const { testUserData, testConfigData } = require('../../structures').userTestData; +import { userTestData } from '../../structures'; chai.use(chaiHttp); +const { testUserData, testConfigData } = userTestData; + export const createMockUser = (): any => { return chai .request(app) diff --git a/packages/server/test/routes/helpers/index.ts b/packages/server/test/routes/helpers/index.ts index 9ae2b3a..fdb7167 100644 --- a/packages/server/test/routes/helpers/index.ts +++ b/packages/server/test/routes/helpers/index.ts @@ -1 +1 @@ -export * as userHelpers from './users'; +export * as userHelpers from './helpers'; From cf3d3ccead8bdb894c7e961372cbcab3b11ea844 Mon Sep 17 00:00:00 2001 From: Michael Ossig Date: Fri, 18 Mar 2022 15:37:35 -0400 Subject: [PATCH 4/4] finished initial endpoint tests, refactored --- .../test/{routes => }/helpers/helpers.ts | 4 +- .../server/test/{routes => }/helpers/index.ts | 0 .../routes/adyen-endpoints/payments.test.ts | 0 .../routes/adyen-endpoints/sessions.test.ts | 0 packages/server/test/routes/auth/auth.test.ts | 197 ++++++++++++++++++ .../test/routes/users/configurations.test.ts | 2 +- .../server/test/routes/users/users.test.ts | 2 +- 7 files changed, 201 insertions(+), 4 deletions(-) rename packages/server/test/{routes => }/helpers/helpers.ts (92%) rename packages/server/test/{routes => }/helpers/index.ts (100%) delete mode 100644 packages/server/test/routes/adyen-endpoints/payments.test.ts delete mode 100644 packages/server/test/routes/adyen-endpoints/sessions.test.ts create mode 100644 packages/server/test/routes/auth/auth.test.ts diff --git a/packages/server/test/routes/helpers/helpers.ts b/packages/server/test/helpers/helpers.ts similarity index 92% rename from packages/server/test/routes/helpers/helpers.ts rename to packages/server/test/helpers/helpers.ts index 846ea77..5519c87 100644 --- a/packages/server/test/routes/helpers/helpers.ts +++ b/packages/server/test/helpers/helpers.ts @@ -1,7 +1,7 @@ import chai from 'chai'; import chaiHttp from 'chai-http'; -import { app } from '../../../index'; -import { userTestData } from '../../structures'; +import { app } from '../../index'; +import { userTestData } from '../structures'; chai.use(chaiHttp); diff --git a/packages/server/test/routes/helpers/index.ts b/packages/server/test/helpers/index.ts similarity index 100% rename from packages/server/test/routes/helpers/index.ts rename to packages/server/test/helpers/index.ts diff --git a/packages/server/test/routes/adyen-endpoints/payments.test.ts b/packages/server/test/routes/adyen-endpoints/payments.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/server/test/routes/adyen-endpoints/sessions.test.ts b/packages/server/test/routes/adyen-endpoints/sessions.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/server/test/routes/auth/auth.test.ts b/packages/server/test/routes/auth/auth.test.ts new file mode 100644 index 0000000..db122cf --- /dev/null +++ b/packages/server/test/routes/auth/auth.test.ts @@ -0,0 +1,197 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import { User } from '../../../models'; +import { userTestData } from '../../structures'; +import { JWT_SECRET, TEST_DATABASE_URL } from '../../../config'; +import { app, runServer, closeServer } from '../../../index'; + +const assert = chai.assert; + +chai.use(chaiHttp); + +const { + testUserData: { username, password, email } +} = userTestData; + +describe('Authorization API', () => { + let _id: string; + before(() => { + return runServer(TEST_DATABASE_URL); + }); + + after(() => { + return closeServer(); + }); + + beforeEach(async () => { + const hashedPassword = await User.hashPassword(password); + if (hashedPassword) { + const { id } = await User.create({ + username, + password: hashedPassword, + email + }); + if (id) { + _id = id; + } + } + }); + + afterEach(() => { + return User.deleteOne({ _id }); + }); + + describe('/login', () => { + it('Should reject requests with no credentials', async () => { + try { + return await chai.request(app).post('/auth/login'); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 400); + } + }); + + it('Should reject requests with incorrect usernames', async () => { + try { + return await chai.request(app).post('/auth/login').auth('wrongUsername', password); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 400); + } + }); + + it('Should reject requests with incorrect passwords', async () => { + try { + return await chai.request(app).post('/auth/login').auth(username, 'wrongPassword'); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 400); + } + }); + + it('Should return a valid auth token', async () => { + const res = await chai.request(app).post('/auth/login').send({ username, password }); + assert.equal(res.status, 200); + assert.equal(typeof res.body, 'object'); + const token = res.body.authToken; + assert.equal(typeof token, 'string'); + const payload: any = jwt.verify(token, JWT_SECRET, { + algorithms: ['HS256'] + }); + assert.equal(payload.user.username, username, 'failed username match'); + assert.equal(payload.user._id, _id, 'failed id match'); + }); + }); + + describe('/auth/refresh', () => { + it('Should reject requests with no credentials', async () => { + try { + return await chai.request(app).post('/auth/refresh'); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 401); + } + }); + + it('Should reject requests with an invalid token', async () => { + const token = jwt.sign( + { + username, + email + }, + 'wrongSecret', + { + algorithm: 'HS256', + expiresIn: '7d' + } + ); + + try { + return await chai.request(app).post('/auth/refresh').set('Authorization', `Bearer ${token}`); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 401); + } + }); + + it('Should reject requests with an expired token', async () => { + const token = jwt.sign( + { + user: { + username, + email + }, + exp: Math.floor(Date.now() / 1000) - 10 // Expired ten seconds ago + }, + JWT_SECRET, + { + algorithm: 'HS256', + subject: username + } + ); + + try { + return await chai.request(app).post('/auth/refresh').set('authorization', `Bearer ${token}`); + } catch (err: any) { + if (err instanceof chai.AssertionError) { + throw err; + } + + const res = err.response; + assert.equal(res.status, 401); + } + }); + + it('Should return a valid auth token with a newer expiry date', async () => { + const token = jwt.sign( + { + user: { + username, + email + } + }, + JWT_SECRET, + { + algorithm: 'HS256', + subject: username, + expiresIn: '7d' + } + ); + const decoded: any = jwt.decode(token); + + const res = await chai.request(app).post('/auth/refresh').set('authorization', `Bearer ${token}`); + assert.equal(res.status, 200); + assert.equal(typeof res.body, 'object'); + + const token_2 = res.body.authToken; + assert.equal(typeof token_2, 'string'); + + const payload: any = jwt.verify(token_2, JWT_SECRET, { + algorithms: ['HS256'] + }); + assert.deepEqual(payload.user, { username, email }); + assert.isAtLeast(decoded.exp, payload.exp); + }); + }); +}); diff --git a/packages/server/test/routes/users/configurations.test.ts b/packages/server/test/routes/users/configurations.test.ts index 018fde4..25d5c96 100644 --- a/packages/server/test/routes/users/configurations.test.ts +++ b/packages/server/test/routes/users/configurations.test.ts @@ -3,7 +3,7 @@ import mongoose from 'mongoose'; import chaiHttp from 'chai-http'; import { userTestData } from '../../structures'; import { TEST_DATABASE_URL } from '../../../config'; -import { userHelpers } from '../helpers'; +import { userHelpers } from '../../helpers'; import { app, runServer, closeServer } from '../../../index'; const assert = chai.assert; diff --git a/packages/server/test/routes/users/users.test.ts b/packages/server/test/routes/users/users.test.ts index 7b61d87..86b6330 100644 --- a/packages/server/test/routes/users/users.test.ts +++ b/packages/server/test/routes/users/users.test.ts @@ -3,7 +3,7 @@ import mongoose from 'mongoose'; import chaiHttp from 'chai-http'; import { userTestData } from '../../structures'; import { TEST_DATABASE_URL } from '../../../config'; -import { userHelpers } from '../helpers'; +import { userHelpers } from '../../helpers'; import { app, runServer, closeServer } from '../../../index'; const assert = chai.assert;