Skip to content

Commit a085c73

Browse files
committed
help
1 parent 85e759d commit a085c73

File tree

9 files changed

+91
-28
lines changed

9 files changed

+91
-28
lines changed

cloudformation/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Mappings:
4040
HostedZoneId: Z04502822NVIA85WM2SML
4141
ApiDomainName: "aws.qa.acmuiuc.org"
4242
ValidCorsOrigins: ["*"]
43-
AadValidClientId: "251efa82-f589-42e1-9ebb-e214a4f40a0f"
43+
AadValidClientId: "39c28870-94e4-47ee-b4fb-affe0bf96c9f"
4444
prod:
4545
ApiCertificateArn: arn:aws:acm:us-east-1:298118738376:certificate/6142a0e2-d62f-478e-bf15-5bdb616fe705
4646
HostedZoneId: Z05246633460N5MEB9DBF
@@ -98,6 +98,7 @@ Resources:
9898
Variables:
9999
RunEnvironment: !Ref RunEnvironment
100100
ValidCorsOrigins: !Join [",", !FindInMap [ApiGwConfig, !Ref RunEnvironment, ValidCorsOrigins]]
101+
AadValidClientId: !FindInMap [ApiGwConfig, !Ref RunEnvironment, AadValidClientId]
101102
VpcConfig:
102103
Ipv6AllowedForDualStack: !If [ShouldAttachVpc, True, !Ref AWS::NoValue]
103104
SecurityGroupIds: !If [ShouldAttachVpc, !FindInMap [EnvironmentToCidr, !Ref RunEnvironment, SecurityGroupIds], !Ref AWS::NoValue]

dist/index.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint import/no-nodejs-modules: ["error", {"allow": ["crypto"]}] */
22
import { randomUUID } from "crypto";
33
import fastify from "fastify";
4-
import { fastifyAuthPlugin } from "./plugins/auth.js";
5-
import FastifyAuthProvider from '@fastify/auth';
6-
import { EventsApiRoles } from "./roles.js";
4+
import FastifyAuthProvider from "@fastify/auth";
5+
import fastifyAuthPlugin from "./plugins/auth.js";
6+
import protectedRoute from "./routes/protected.js";
77
const now = () => Date.now();
88
async function init() {
99
const app = fastify({
@@ -21,7 +21,7 @@ async function init() {
2121
});
2222
await app.register(fastifyAuthPlugin);
2323
await app.register(FastifyAuthProvider);
24-
app.runEnvironment = process.env.RunEnvironment ?? 'dev';
24+
app.runEnvironment = process.env.RunEnvironment ?? "dev";
2525
app.addHook("onRequest", (req, _, done) => {
2626
req.startTime = now();
2727
req.log.info({ url: req.raw.url }, "received request");
@@ -36,15 +36,9 @@ async function init() {
3636
done();
3737
});
3838
app.get("/api/v1/healthz", (_, reply) => reply.send({ message: "UP" }));
39-
app.get("/api/v1/protected", async (_, reply) => {
40-
app.auth([
41-
app.authenticate,
42-
async (request, reply) => {
43-
await app.authorize(request, reply, [EventsApiRoles.MANAGER]);
44-
}
45-
]);
46-
reply.send({ message: "UP" });
47-
});
39+
await app.register(async (api, _options) => {
40+
api.register(protectedRoute, { prefix: "/protected" });
41+
}, { prefix: "/api/v1" });
4842
return app;
4943
}
5044
if (import.meta.url === `file://${process.argv[1]}`) {

src/errors/index.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ interface BaseErrorParams<T extends string> {
55
httpStatusCode: number;
66
}
77

8-
abstract class BaseError<T extends string> extends Error {
8+
export abstract class BaseError<T extends string> extends Error {
99
public name: T;
1010

1111
public id: number;
@@ -20,22 +20,51 @@ abstract class BaseError<T extends string> extends Error {
2020
this.id = id;
2121
this.message = message;
2222
this.httpStatusCode = httpStatusCode;
23+
if (Error.captureStackTrace) {
24+
Error.captureStackTrace(this, this.constructor);
25+
}
26+
}
27+
toString() {
28+
return `Error ${this.id} (${this.name}): ${this.message}\n\n${this.stack}`
2329
}
2430
}
2531

2632
export class UnauthorizedError extends BaseError<"UnauthorizedError"> {
2733
constructor({ message }: { message: string }) {
28-
super({ name: "UnauthorizedError", id: 100, message, httpStatusCode: 401 });
34+
super({ name: "UnauthorizedError", id: 101, message, httpStatusCode: 401 });
2935
}
3036
}
3137

3238
export class UnauthenticatedError extends BaseError<"UnauthenticatedError"> {
3339
constructor({ message }: { message: string }) {
3440
super({
3541
name: "UnauthenticatedError",
36-
id: 101,
42+
id: 102,
3743
message,
3844
httpStatusCode: 403,
3945
});
4046
}
4147
}
48+
49+
export class InternalServerError extends BaseError<"InternalServerError"> {
50+
constructor() {
51+
super({
52+
name: "InternalServerError",
53+
id: 100,
54+
message: "An internal server error occurred. Please try again or contact support.",
55+
httpStatusCode: 500,
56+
});
57+
}
58+
}
59+
60+
61+
export class NotFoundError extends BaseError<"NotFoundError"> {
62+
constructor({endpointName}: {endpointName: string}) {
63+
super({
64+
name: "NotFoundError",
65+
id: 103,
66+
message: `${endpointName} is not a valid URL.`,
67+
httpStatusCode: 404,
68+
});
69+
}
70+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fastify, { FastifyInstance } from "fastify";
44
import FastifyAuthProvider from "@fastify/auth";
55
import fastifyAuthPlugin from "./plugins/auth.js";
66
import protectedRoute from "./routes/protected.js";
7+
import errorHandlerPlugin from "./plugins/errorHandler.js";
78

89
const now = () => Date.now();
910

@@ -23,6 +24,7 @@ async function init() {
2324
});
2425
await app.register(fastifyAuthPlugin);
2526
await app.register(FastifyAuthProvider);
27+
await app.register(errorHandlerPlugin);
2628
app.runEnvironment = process.env.RunEnvironment ?? "dev";
2729
app.addHook("onRequest", (req, _, done) => {
2830
req.startTime = now();

src/plugins/auth.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
22
import fp from "fastify-plugin";
3-
import { UnauthenticatedError, UnauthorizedError } from "../errors/index.js";
4-
import { EventsApiRoles } from "../roles.js";
3+
import { BaseError, UnauthenticatedError, UnauthorizedError } from "../errors/index.js";
4+
import { AppRoles } from "../roles.js";
55

6-
// const GroupRoleMapping: Record<string, EventsApiRoles[]> = {};
6+
// const GroupRoleMapping: Record<string, AppRoles[]> = {};
77

88
const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
99
fastify.decorate(
@@ -13,8 +13,14 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
1313
_reply: FastifyReply,
1414
): Promise<void> {
1515
try {
16-
request.log.info("Authenticating JWT");
17-
} catch (_: unknown) {
16+
const clientId = process.env.AadValidClientId;
17+
if (!clientId) {
18+
throw new UnauthenticatedError({message: "Server could not find valid AAD Client ID."})
19+
}
20+
} catch (err: unknown) {
21+
if (err instanceof BaseError) {
22+
throw err;
23+
}
1824
throw new UnauthenticatedError({ message: "Could not verify JWT." });
1925
}
2026
},
@@ -24,7 +30,7 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
2430
async function (
2531
request: FastifyRequest,
2632
_reply: FastifyReply,
27-
_validRoles: EventsApiRoles[],
33+
_validRoles: AppRoles[],
2834
): Promise<void> {
2935
try {
3036
request.log.info("Authorizing JWT");

src/plugins/errorHandler.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import fastify, { FastifyReply, FastifyRequest } from 'fastify';
2+
import fp from 'fastify-plugin';
3+
import { BaseError, InternalServerError, NotFoundError } from '../errors/index.js';
4+
import { request } from 'http';
5+
6+
const errorHandlerPlugin = fp(async(fastify) => {
7+
fastify.setErrorHandler((err: unknown, request: FastifyRequest, reply: FastifyReply) => {
8+
let finalErr = new InternalServerError();
9+
if (err instanceof BaseError) {
10+
finalErr = err;
11+
}
12+
if (err instanceof BaseError) {
13+
request.log.error({errId: err.id, errName: err.name}, finalErr.toString())
14+
} else if (err instanceof Error) {
15+
request.log.error({errName: err.name}, 'Native unhandled error: response sent to client.')
16+
} else {
17+
request.log.error('Native unhandled error: response sent to client.')
18+
}
19+
reply.status(finalErr.httpStatusCode).type('application/json').send({
20+
error: true,
21+
name: finalErr.name,
22+
id: finalErr.id,
23+
message: finalErr.message,
24+
})
25+
})
26+
fastify.setNotFoundHandler((request: FastifyRequest) => {
27+
throw new NotFoundError({endpointName: request.url});
28+
})
29+
})
30+
31+
export default errorHandlerPlugin;

src/roles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/* eslint-disable import/prefer-default-export */
2-
export enum EventsApiRoles {
2+
export enum AppRoles {
33
MANAGER,
44
}

src/routes/protected.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FastifyPluginAsync } from "fastify";
2-
import { EventsApiRoles } from "../roles.js";
2+
import { AppRoles } from "../roles.js";
33

44
const protectedRoute: FastifyPluginAsync = async (fastify, _options) => {
55
fastify.get(
@@ -8,7 +8,7 @@ const protectedRoute: FastifyPluginAsync = async (fastify, _options) => {
88
onRequest: fastify.auth([
99
fastify.authenticate,
1010
async (request, reply) => {
11-
fastify.authorize(request, reply, [EventsApiRoles.MANAGER]);
11+
fastify.authorize(request, reply, [AppRoles.MANAGER]);
1212
},
1313
]),
1414
},

src/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FastifyRequest, FastifyInstance, FastifyReply } from "fastify";
2-
import { EventsApiRoles } from "./roles.ts";
2+
import { AppRoles } from "./roles.ts";
33
declare module "fastify" {
44
interface FastifyInstance {
55
authenticate: (
@@ -9,7 +9,7 @@ declare module "fastify" {
99
authorize: (
1010
request: FastifyRequest,
1111
reply: FastifyReply,
12-
validRoles: EventsApiRoles[],
12+
validRoles: AppRoles[],
1313
) => Promise<void>;
1414
runEnvironment: string;
1515
}

0 commit comments

Comments
 (0)