mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 16:00:32 +00:00
Merge pull request #68 from brendan-ch/chore/rate-limits
chore/rate-limits
This commit is contained in:
@@ -8,6 +8,13 @@ APNS_BUNDLE_ID=
|
|||||||
# base64-encoded APNs private key
|
# base64-encoded APNs private key
|
||||||
APNS_PRIVATE_KEY=
|
APNS_PRIVATE_KEY=
|
||||||
|
|
||||||
|
# control parking data logging
|
||||||
PARKING_LOGGING_INTERVAL_MS=
|
PARKING_LOGGING_INTERVAL_MS=
|
||||||
PARKING_HISTORICAL_AVERAGE_MINIMUM_INTERVAL=
|
PARKING_HISTORICAL_AVERAGE_MINIMUM_INTERVAL=
|
||||||
PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN=
|
PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN=
|
||||||
|
|
||||||
|
# control rate limiting features
|
||||||
|
RATE_LIMITS_DISABLED=
|
||||||
|
RATE_LIMIT_WINDOW_MS=
|
||||||
|
RATE_LIMIT_DELAY_AFTER_REQUESTS=
|
||||||
|
RATE_LIMIT_DELAY_MULTIPLIER_MS=
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ x-common-environment: &common-server-environment
|
|||||||
APNS_PRIVATE_KEY: ${APNS_PRIVATE_KEY}
|
APNS_PRIVATE_KEY: ${APNS_PRIVATE_KEY}
|
||||||
PARKING_LOGGING_INTERVAL_MS: ${PARKING_LOGGING_INTERVAL_MS}
|
PARKING_LOGGING_INTERVAL_MS: ${PARKING_LOGGING_INTERVAL_MS}
|
||||||
REDIS_URL: redis://redis:6379
|
REDIS_URL: redis://redis:6379
|
||||||
|
RATE_LIMITS_DISABLED: ${RATE_LIMITS_DISABLED}
|
||||||
|
RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS}
|
||||||
|
RATE_LIMIT_DELAY_AFTER_REQUESTS: ${RATE_LIMIT_DELAY_AFTER_REQUESTS}
|
||||||
|
RATE_LIMIT_DELAY_MULTIPLIER_MS: ${RATE_LIMIT_DELAY_MULTIPLIER_MS}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dev:
|
dev:
|
||||||
@@ -33,6 +37,7 @@ services:
|
|||||||
- redis-no-persistence
|
- redis-no-persistence
|
||||||
environment:
|
environment:
|
||||||
REDIS_URL: redis://redis-no-persistence:6379
|
REDIS_URL: redis://redis-no-persistence:6379
|
||||||
|
RATE_LIMITS_DISABLED: 1
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/app
|
- .:/usr/src/app
|
||||||
|
|||||||
800
package-lock.json
generated
800
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
|||||||
"@graphql-codegen/typescript": "4.1.2",
|
"@graphql-codegen/typescript": "4.1.2",
|
||||||
"@graphql-codegen/typescript-resolvers": "4.4.1",
|
"@graphql-codegen/typescript-resolvers": "4.4.1",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
|
"@types/express": "^5.0.3",
|
||||||
"@types/jsonwebtoken": "^9.0.8",
|
"@types/jsonwebtoken": "^9.0.8",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/redis": "^4.0.11",
|
"@types/redis": "^4.0.11",
|
||||||
@@ -26,6 +27,10 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/server": "^4.11.2",
|
"@apollo/server": "^4.11.2",
|
||||||
|
"@as-integrations/express5": "^1.1.2",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-rate-limit": "^8.0.1",
|
||||||
|
"express-slow-down": "^3.0.0",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.10.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"redis": "^4.7.0"
|
"redis": "^4.7.0"
|
||||||
|
|||||||
@@ -3,3 +3,9 @@ export const PARKING_LOGGING_INTERVAL_MS = 10000;
|
|||||||
export const PARKING_HISTORICAL_AVERAGE_MINIMUM_INTERVAL = 1000;
|
export const PARKING_HISTORICAL_AVERAGE_MINIMUM_INTERVAL = 1000;
|
||||||
|
|
||||||
export const PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN = 60000 * 60 * 24;
|
export const PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN = 60000 * 60 * 24;
|
||||||
|
|
||||||
|
export const RATE_LIMITS_DISABLED = true;
|
||||||
|
export const RATE_LIMIT_WINDOW_MS = 10000;
|
||||||
|
export const RATE_LIMIT_DELAY_AFTER_REQUESTS = 10000;
|
||||||
|
export const RATE_LIMIT_DELAY_MULTIPLIER_MS = 1000;
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,15 @@ export const PARKING_HISTORICAL_AVERAGE_MINIMUM_INTERVAL = process.env.PARKING_H
|
|||||||
export const PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN = process.env.PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN
|
export const PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN = process.env.PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN
|
||||||
? parseInt(process.env.PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN)
|
? parseInt(process.env.PARKING_HISTORICAL_AVERAGE_MAXIMUM_TIMESPAN)
|
||||||
: 60000 * 60 * 24;
|
: 60000 * 60 * 24;
|
||||||
|
|
||||||
|
export const RATE_LIMITS_DISABLED = process.env.RATE_LIMITS_DISABLED === "1";
|
||||||
|
export const RATE_LIMIT_WINDOW_MS = process.env.RATE_LIMIT_WINDOW_MS
|
||||||
|
? parseInt(process.env.RATE_LIMIT_WINDOW_MS)
|
||||||
|
: 10000;
|
||||||
|
export const RATE_LIMIT_DELAY_AFTER_REQUESTS = process.env.RATE_LIMIT_DELAY_AFTER_REQUESTS
|
||||||
|
? parseInt(process.env.RATE_LIMIT_DELAY_AFTER_REQUESTS)
|
||||||
|
: 10000;
|
||||||
|
export const RATE_LIMIT_DELAY_MULTIPLIER_MS = process.env.RATE_LIMIT_DELAY_MULTIPLIER_MS
|
||||||
|
? parseInt(process.env.RATE_LIMIT_DELAY_MULTIPLIER_MS)
|
||||||
|
: 1000;
|
||||||
|
|
||||||
|
|||||||
66
src/index.ts
66
src/index.ts
@@ -1,13 +1,18 @@
|
|||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { ApolloServer } from "@apollo/server";
|
import { ApolloServer } from "@apollo/server";
|
||||||
import { startStandaloneServer } from "@apollo/server/standalone";
|
|
||||||
import { MergedResolvers } from "./MergedResolvers";
|
import { MergedResolvers } from "./MergedResolvers";
|
||||||
import { ServerContext } from "./ServerContext";
|
import { ServerContext } from "./ServerContext";
|
||||||
import { loadShuttleTestData } from "./loaders/shuttle/loadShuttleTestData";
|
|
||||||
import { InterchangeSystem, InterchangeSystemBuilderArguments } from "./entities/InterchangeSystem";
|
import { InterchangeSystem, InterchangeSystemBuilderArguments } from "./entities/InterchangeSystem";
|
||||||
import { ChapmanApiBasedParkingRepositoryLoader } from "./loaders/parking/ChapmanApiBasedParkingRepositoryLoader";
|
import { ChapmanApiBasedParkingRepositoryLoader } from "./loaders/parking/ChapmanApiBasedParkingRepositoryLoader";
|
||||||
import { supportedIntegrationTestSystems } from "./loaders/supportedIntegrationTestSystems";
|
import express from "express";
|
||||||
import { loadParkingTestData } from "./loaders/parking/loadParkingTestData";
|
import { expressMiddleware } from "@as-integrations/express5";
|
||||||
|
import {
|
||||||
|
RATE_LIMIT_DELAY_AFTER_REQUESTS,
|
||||||
|
RATE_LIMIT_DELAY_MULTIPLIER_MS,
|
||||||
|
RATE_LIMIT_WINDOW_MS,
|
||||||
|
RATE_LIMITS_DISABLED
|
||||||
|
} from "./environment";
|
||||||
|
import slowDown from "express-slow-down";
|
||||||
|
|
||||||
const typeDefs = readFileSync("./schema.graphqls", "utf8");
|
const typeDefs = readFileSync("./schema.graphqls", "utf8");
|
||||||
|
|
||||||
@@ -27,37 +32,18 @@ async function main() {
|
|||||||
resolvers: MergedResolvers,
|
resolvers: MergedResolvers,
|
||||||
introspection: process.env.NODE_ENV !== "production",
|
introspection: process.env.NODE_ENV !== "production",
|
||||||
});
|
});
|
||||||
|
await server.start();
|
||||||
|
|
||||||
let systems: InterchangeSystem[];
|
let systems: InterchangeSystem[];
|
||||||
|
|
||||||
if (process.argv.length > 2 && process.argv[2] == "integration-testing") {
|
|
||||||
console.log("Using integration testing setup")
|
|
||||||
|
|
||||||
systems = await Promise.all(supportedIntegrationTestSystems.map(
|
|
||||||
async (systemArguments) => {
|
|
||||||
const system = InterchangeSystem.buildForTesting(systemArguments);
|
|
||||||
|
|
||||||
// TODO: Have loading of different data for different systems in the future
|
|
||||||
await loadShuttleTestData(system.shuttleRepository);
|
|
||||||
if (system.parkingRepository) {
|
|
||||||
await loadParkingTestData(system.parkingRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
return system;
|
|
||||||
}
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
systems = await Promise.all(supportedSystems.map(
|
systems = await Promise.all(supportedSystems.map(
|
||||||
async (systemArguments) => {
|
async (systemArguments) => {
|
||||||
return await InterchangeSystem.build(systemArguments);
|
return await InterchangeSystem.build(systemArguments);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
const { url } = await startStandaloneServer(server, {
|
const app = express();
|
||||||
listen: {
|
const options = {
|
||||||
port: process.env.PORT ? parseInt(process.env.PORT) : 4000,
|
|
||||||
},
|
|
||||||
context: async () => {
|
context: async () => {
|
||||||
return {
|
return {
|
||||||
systems,
|
systems,
|
||||||
@@ -70,10 +56,34 @@ async function main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (RATE_LIMITS_DISABLED) {
|
||||||
|
app.use(
|
||||||
|
"/",
|
||||||
|
express.json(),
|
||||||
|
expressMiddleware(server, options)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const limiter = slowDown({
|
||||||
|
windowMs: RATE_LIMIT_WINDOW_MS,
|
||||||
|
delayAfter: RATE_LIMIT_DELAY_AFTER_REQUESTS,
|
||||||
|
delayMs: (hits) => {
|
||||||
|
return hits * RATE_LIMIT_DELAY_MULTIPLIER_MS;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
app.use(
|
||||||
|
"/",
|
||||||
|
express.json(),
|
||||||
|
limiter,
|
||||||
|
expressMiddleware(server, options),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = process.env.PORT ? parseInt(process.env.PORT) : 4000;
|
||||||
console.log(`Server ready at: ${url}`);
|
app.listen(port, () => {
|
||||||
|
console.log(`Server ready at port ${port}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
Reference in New Issue
Block a user