Skip to content

Commit 898dc96

Browse files
authored
Enhance request with IP and location (#202)
1 parent e69a0a5 commit 898dc96

File tree

8 files changed

+101
-32
lines changed

8 files changed

+101
-32
lines changed

src/api/index.ts

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,75 @@
1-
/* eslint import/no-nodejs-modules: ["error", {"allow": ["crypto"]}] */
1+
/* eslint import/no-nodejs-modules: ["error", {"allow": ["crypto", "path", "url"]}] */
22

33
import { randomUUID } from "crypto";
4+
import { fileURLToPath } from "url";
5+
import path from "path";
46
import fastify, { FastifyInstance } from "fastify";
5-
import FastifyAuthProvider from "@fastify/auth";
6-
import fastifyStatic from "@fastify/static";
7-
import fastifyAuthPlugin, { getSecretValue } from "./plugins/auth.js";
8-
import protectedRoute from "./routes/protected.js";
9-
import errorHandlerPlugin from "./plugins/errorHandler.js";
107
import { RunEnvironment, runEnvironments } from "../common/roles.js";
118
import { InternalServerError } from "../common/errors/index.js";
12-
import eventsPlugin from "./routes/events.js";
13-
import cors from "@fastify/cors";
149
import {
1510
environmentConfig,
1611
genericConfig,
1712
SecretConfig,
1813
} from "../common/config.js";
19-
import organizationsPlugin from "./routes/organizations.js";
20-
import authorizeFromSchemaPlugin from "./plugins/authorizeFromSchema.js";
21-
import evaluatePoliciesPlugin from "./plugins/evaluatePolicies.js";
22-
import icalPlugin from "./routes/ics.js";
23-
import vendingPlugin from "./routes/vending.js";
2414
import * as dotenv from "dotenv";
25-
import iamRoutes from "./routes/iam.js";
26-
import ticketsPlugin from "./routes/tickets.js";
27-
import linkryRoutes from "./routes/linkry.js";
2815
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
2916
import NodeCache from "node-cache";
3017
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
3118
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
32-
import mobileWalletRoute from "./routes/mobileWallet.js";
33-
import stripeRoutes from "./routes/stripe.js";
34-
import membershipPlugin from "./routes/membership.js";
35-
import path from "path"; // eslint-disable-line import/no-nodejs-modules
36-
import roomRequestRoutes from "./routes/roomRequests.js";
37-
import logsPlugin from "./routes/logs.js";
38-
3919
import {
4020
fastifyZodOpenApiPlugin,
4121
fastifyZodOpenApiTransform,
4222
fastifyZodOpenApiTransformObject,
4323
serializerCompiler,
4424
validatorCompiler,
4525
} from "fastify-zod-openapi";
46-
import { ZodOpenApiVersion } from "zod-openapi";
26+
import { type ZodOpenApiVersion } from "zod-openapi";
4727
import { withTags } from "./components/index.js";
28+
import RedisModule from "ioredis";
29+
30+
/** BEGIN EXTERNAL PLUGINS */
31+
import fastifyIp from "fastify-ip";
32+
import cors from "@fastify/cors";
33+
import FastifyAuthProvider from "@fastify/auth";
34+
import fastifyStatic from "@fastify/static";
35+
/** END EXTERNAL PLUGINS */
36+
37+
/** BEGIN INTERNAL PLUGINS */
38+
import locationPlugin from "./plugins/location.js";
39+
import fastifyAuthPlugin, { getSecretValue } from "./plugins/auth.js";
40+
import errorHandlerPlugin from "./plugins/errorHandler.js";
41+
import authorizeFromSchemaPlugin from "./plugins/authorizeFromSchema.js";
42+
import evaluatePoliciesPlugin from "./plugins/evaluatePolicies.js";
43+
/** END INTERNAL PLUGINS */
44+
45+
/** BEGIN ROUTES */
46+
import organizationsPlugin from "./routes/organizations.js";
47+
import icalPlugin from "./routes/ics.js";
48+
import vendingPlugin from "./routes/vending.js";
49+
import iamRoutes from "./routes/iam.js";
50+
import ticketsPlugin from "./routes/tickets.js";
51+
import linkryRoutes from "./routes/linkry.js";
52+
import mobileWalletRoute from "./routes/mobileWallet.js";
53+
import stripeRoutes from "./routes/stripe.js";
54+
import membershipPlugin from "./routes/membership.js";
55+
import roomRequestRoutes from "./routes/roomRequests.js";
56+
import logsPlugin from "./routes/logs.js";
4857
import apiKeyRoute from "./routes/apiKey.js";
4958
import clearSessionRoute from "./routes/clearSession.js";
50-
import RedisModule from "ioredis";
51-
import { fileURLToPath } from "url"; // eslint-disable-line import/no-nodejs-modules
59+
import protectedRoute from "./routes/protected.js";
60+
import eventsPlugin from "./routes/events.js";
61+
/** END ROUTES */
62+
5263
const __filename = fileURLToPath(import.meta.url);
5364
const __dirname = path.dirname(__filename);
5465

5566
dotenv.config();
5667

5768
const now = () => Date.now();
69+
const isRunningInLambda =
70+
process.env.LAMBDA_TASK_ROOT || process.env.AWS_LAMBDA_FUNCTION_NAME;
5871

5972
async function init(prettyPrint: boolean = false, initClients: boolean = true) {
60-
const isRunningInLambda =
61-
process.env.LAMBDA_TASK_ROOT || process.env.AWS_LAMBDA_FUNCTION_NAME;
6273
let isSwaggerServer = false;
6374
const transport = prettyPrint
6475
? {
@@ -95,6 +106,7 @@ async function init(prettyPrint: boolean = false, initClients: boolean = true) {
95106
await app.register(evaluatePoliciesPlugin);
96107
await app.register(errorHandlerPlugin);
97108
await app.register(fastifyZodOpenApiPlugin);
109+
await app.register(locationPlugin);
98110
if (!isRunningInLambda) {
99111
try {
100112
const fastifySwagger = import("@fastify/swagger");
@@ -278,6 +290,14 @@ async function init(prettyPrint: boolean = false, initClients: boolean = true) {
278290
await app.refreshSecretConfig();
279291
app.redisClient = new RedisModule.default(app.secretConfig.redis_url);
280292
}
293+
if (isRunningInLambda) {
294+
await app.register(fastifyIp.default, {
295+
order: ["x-forwarded-for"],
296+
strict: true,
297+
isAWS: false,
298+
});
299+
}
300+
281301
app.addHook("onRequest", (req, _, done) => {
282302
req.startTime = now();
283303
const hostname = req.hostname;
@@ -337,6 +357,7 @@ async function init(prettyPrint: boolean = false, initClients: boolean = true) {
337357
origin: app.environmentConfig.ValidCorsOrigins,
338358
methods: ["GET", "HEAD", "POST", "PATCH", "DELETE"],
339359
});
360+
340361
app.addHook("onSend", async (request, reply) => {
341362
reply.header("X-Request-Id", request.id);
342363
});

src/api/lambda.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const handler = async (event: APIGatewayEvent, context: Context) => {
2929
isBase64Encoded: false,
3030
};
3131
}
32+
delete event.headers["x-origin-verify"];
3233
}
3334
// else proceed with handler logic
3435
return await realHandler(event, context).catch((e) => {

src/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"dotenv": "^16.5.0",
4141
"esbuild": "^0.25.3",
4242
"fastify": "^5.3.2",
43+
"fastify-ip": "^1.2.0",
4344
"fastify-plugin": "^5.0.1",
4445
"fastify-raw-body": "^5.0.0",
4546
"fastify-zod-openapi": "^5.0.1",

src/api/plugins/errorHandler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ const errorHandlerPlugin = fp(async (fastify) => {
4747
},
4848
);
4949
fastify.setNotFoundHandler((request: FastifyRequest) => {
50-
throw new NotFoundError({ endpointName: request.url });
50+
throw new NotFoundError({
51+
endpointName: `${request.method} ${request.url}`,
52+
});
5153
});
5254
});
5355

src/api/plugins/location.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import fp from "fastify-plugin";
2+
3+
const locationPlugin = fp(async (fastify, opts) => {
4+
const processHeader = (headerValue: string | string[] | undefined) => {
5+
if (Array.isArray(headerValue)) {
6+
return headerValue.join(",");
7+
}
8+
return headerValue;
9+
};
10+
11+
fastify.decorateRequest("location", {
12+
getter() {
13+
return {
14+
country: processHeader(this.headers["cloudfront-viewer-country"]),
15+
city: processHeader(this.headers["cloudfront-viewer-city"]),
16+
region: processHeader(this.headers["cloudfront-viewer-country-region"]),
17+
latitude: processHeader(this.headers["cloudfront-viewer-latitude"]),
18+
longitude: processHeader(this.headers["cloudfront-viewer-longitude"]),
19+
postalCode: processHeader(
20+
this.headers["cloudfront-viewer-postal-code"],
21+
),
22+
};
23+
},
24+
});
25+
});
26+
27+
export default locationPlugin;

src/api/routes/apiKey.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ Key ID: acmuiuc_${keyId}
105105
106106
Key Description: ${description}
107107
108-
IP address: ${request.ip}.
109-
108+
IP address: ${request.ip}
109+
${request.location.city && request.location.region && request.location.country ? `\nLocation: ${request.location.city}, ${request.location.region}, ${request.location.country}\n` : ""}
110110
Roles: ${roles.join(", ")}.
111111
112112
If you did not create this API key, please secure your account and notify the ACM Infrastructure team.
@@ -210,7 +210,7 @@ This email confirms that an API key for the Core API has been deleted from your
210210
Key ID: acmuiuc_${keyId}
211211
212212
IP address: ${request.ip}.
213-
213+
${request.location.city && request.location.region && request.location.country ? `\nLocation: ${request.location.city}, ${request.location.region}, ${request.location.country}\n` : ""}
214214
If you did not delete this API key, please secure your account and notify the ACM Infrastructure team.
215215
`,
216216
callToActionButton: {

src/api/types.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ import { AvailableAuthorizationPolicy } from "common/policies/definition.js";
1212
import type RedisModule from "ioredis";
1313
type Redis = RedisModule.default;
1414

15+
interface CloudfrontLocation {
16+
country: string | undefined;
17+
city: string | undefined;
18+
region: string | undefined;
19+
latitude: string | undefined;
20+
longitude: string | undefined;
21+
postalCode: string | undefined;
22+
}
23+
1524
declare module "fastify" {
1625
interface FastifyInstance {
1726
authenticate: (
@@ -45,6 +54,7 @@ declare module "fastify" {
4554
userRoles?: Set<AppRoles>;
4655
tokenPayload?: AadToken;
4756
policyRestrictions?: AvailableAuthorizationPolicy[];
57+
location: CloudfrontLocation;
4858
}
4959
}
5060

yarn.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5633,6 +5633,13 @@ fastest-levenshtein@^1.0.16:
56335633
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
56345634
integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
56355635

5636+
fastify-ip@^1.2.0:
5637+
version "1.2.0"
5638+
resolved "https://registry.yarnpkg.com/fastify-ip/-/fastify-ip-1.2.0.tgz#bd65121e843f407870da11f1a721afe5083f44a1"
5639+
integrity sha512-n7BqGlEMZmaG/zEdrp7/fShBUNWfgT6EKXfC3FERTzuSPZSo8gxfq0kjD8PhAjD1I1o7oqo5Wc/m7jr+Fn+HRg==
5640+
dependencies:
5641+
fastify-plugin "^5.0.1"
5642+
56365643
fastify-plugin@^5.0.0, fastify-plugin@^5.0.1:
56375644
version "5.0.1"
56385645
resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-5.0.1.tgz#82d44e6fe34d1420bb5a4f7bee434d501e41939f"

0 commit comments

Comments
 (0)