mirror of
https://github.com/jlengrand/adyen-web-demo.git
synced 2026-03-10 08:01:24 +00:00
Merge pull request #3 from ossiggy/mo/server-refactor
Mo/server refactor
This commit is contained in:
104
package-lock.json
generated
104
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { updateUserInfo, clearUserInfo } from './reducers/user';
|
||||
export { updateConfigurationInfo, clearConfigurationInfo } from './reducers/configuration';
|
||||
export { userActions, configurationActions } from './reducers';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -23,6 +23,4 @@ export const configurationSlice = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
export const { updateConfigurationInfo, clearConfigurationInfo } = configurationSlice.actions;
|
||||
|
||||
export default configurationSlice.reducer;
|
||||
export const { actions, reducer } = configurationSlice;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -20,6 +20,4 @@ export const userSlice = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
export const { updateUserInfo, clearUserInfo } = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
||||
export const { actions, reducer } = userSlice;
|
||||
|
||||
@@ -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<AppDispatch>();
|
||||
const useAppSelector: TypedUseSelectorHook<RootState> = 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<AppDispatch>();
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<void>((resolve, reject) => {
|
||||
@@ -59,7 +59,7 @@ export const closeServer = () => {
|
||||
return mongoose.disconnect().then(() => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.log('Closing server');
|
||||
return server.close(err => {
|
||||
return server.close((err: any) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { User, Configuration } from './users';
|
||||
export type { UserDocument, ConfigurationDocument } from './types';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Configuration> {}
|
||||
|
||||
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>('Configuration', ConfigurationSchema);
|
||||
export const Configuration: ConfigurationModel = model<Configuration, ConfigurationModel>('Configuration', ConfigurationSchema);
|
||||
|
||||
export default Configuration;
|
||||
|
||||
@@ -50,11 +50,11 @@ UserSchema.method('apiRepr', function () {
|
||||
};
|
||||
});
|
||||
|
||||
UserSchema.method('validatePassword', function (password: string): boolean {
|
||||
UserSchema.method('validatePassword', function (password: string): Promise<boolean> {
|
||||
return compare(password, this.password);
|
||||
});
|
||||
|
||||
UserSchema.static('hashPassword', (password: string): string => hash(password, 10));
|
||||
UserSchema.static('hashPassword', (password: string): Promise<string> => hash(password, 10));
|
||||
|
||||
export const User: UserModel = model<User, UserModel>('User', UserSchema);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export { router as sessionsRouter } from './sessions';
|
||||
export { router as paymentsRouter } from './payments';
|
||||
|
||||
export type { BaseAdyenRequest, InitializationRequest, RequestOptions, PaymentAmount } from './types';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Router } from 'express';
|
||||
import { Router, Request, Response } from 'express';
|
||||
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) => {
|
||||
router.post('/sessionStart', async (req: Request, res: Response) => {
|
||||
const { version, apiKey, payload }: InitializationRequest = req.body;
|
||||
try {
|
||||
const options = {
|
||||
const options: RequestOptions = {
|
||||
url: `${ADYEN_BASE_URL}/${version}/sessions`,
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,3 +29,5 @@ export interface RequestOptions {
|
||||
body: any;
|
||||
json: boolean;
|
||||
}
|
||||
|
||||
export type { PaymentAmount };
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
@@ -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<T extends ParamsDictionary> extends Request {
|
||||
params: T;
|
||||
}
|
||||
|
||||
export const isAuthorizedForAction = (req: AuthorizationRequest<AuthorizedParams>, 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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' });
|
||||
|
||||
11
packages/server/routes/users/types.ts
Normal file
11
packages/server/routes/users/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export type ConfigToUpdate = {
|
||||
name?: string;
|
||||
version?: number;
|
||||
configuration?: string;
|
||||
};
|
||||
|
||||
export type UserToUpdate = {
|
||||
adyenKey?: string;
|
||||
merchantAccounts?: string[];
|
||||
configurations?: string[];
|
||||
};
|
||||
@@ -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' });
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import chai from 'chai';
|
||||
import chaiHttp from 'chai-http';
|
||||
import { app } from '../../../index';
|
||||
const { testUserData, testConfigData } = require('../../structures').userTestData;
|
||||
import { app } from '../../index';
|
||||
import { userTestData } from '../structures';
|
||||
|
||||
chai.use(chaiHttp);
|
||||
|
||||
const { testUserData, testConfigData } = userTestData;
|
||||
|
||||
export const createMockUser = (): any => {
|
||||
return chai
|
||||
.request(app)
|
||||
1
packages/server/test/helpers/index.ts
Normal file
1
packages/server/test/helpers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * as userHelpers from './helpers';
|
||||
197
packages/server/test/routes/auth/auth.test.ts
Normal file
197
packages/server/test/routes/auth/auth.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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']
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"types": ["mocha"],
|
||||
"target": "es6",
|
||||
"rootDir": "./",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user