Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,14 @@ async function init(prettyPrint: boolean = false) {
version: "1.0.0",
},
servers: [
app.runEnvironment === "prod"
? {
url: "https://core.acm.illinois.edu",
description: "Production API server",
}
: {
url: "https://core.aws.qa.acmuiuc.org",
description: "QA API server",
},
{
url: "https://core.acm.illinois.edu",
description: "Production API server",
},
{
url: "https://core.aws.qa.acmuiuc.org",
description: "QA API server",
},
],
tags: [
{
Expand Down Expand Up @@ -146,6 +145,24 @@ async function init(prettyPrint: boolean = false) {
name: "Membership",
description: "Purchasing or checking ACM @ UIUC membership.",
},
{
name: "Tickets/Merchandise",
description: "Handling the tickets and merchandise lifecycle.",
},
{
name: "Mobile Wallet",
description: "Issuing Apple/Google Wallet passes.",
},
{
name: "Stripe",
description:
"Collecting payments for ACM @ UIUC invoices and other services.",
},
{
name: "Room Requests",
description:
"Creating room reservation requests for ACM @ UIUC within University buildings.",
},
],
openapi: "3.0.3" satisfies ZodOpenApiVersion, // If this is not specified, it will default to 3.1.0
},
Expand Down Expand Up @@ -194,7 +211,11 @@ async function init(prettyPrint: boolean = false) {
});
app.get(
"/api/v1/healthz",
{ schema: withTags(["Generic"], {}) },
{
schema: withTags(["Generic"], {
summary: "Verify that the API server is healthy.",
}),
},
(_, reply) => reply.send({ message: "UP" }),
);
await app.register(
Expand Down
4 changes: 4 additions & 0 deletions src/api/routes/events.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import "zod-openapi/extend";
import { FastifyPluginAsync, FastifyRequest } from "fastify";

Check warning on line 2 in src/api/routes/events.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'FastifyRequest' is defined but never used. Allowed unused vars must match /^_/u
import { AppRoles } from "../../common/roles.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

Check warning on line 5 in src/api/routes/events.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'zodToJsonSchema' is defined but never used. Allowed unused vars must match /^_/u
import { OrganizationList } from "../../common/orgs.js";
import {
DeleteItemCommand,
Expand Down Expand Up @@ -37,8 +37,8 @@
FastifyPluginAsyncZodOpenApi,
FastifyZodOpenApiSchema,
FastifyZodOpenApiTypeProvider,
serializerCompiler,

Check warning on line 40 in src/api/routes/events.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'serializerCompiler' is defined but never used. Allowed unused vars must match /^_/u
validatorCompiler,

Check warning on line 41 in src/api/routes/events.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'validatorCompiler' is defined but never used. Allowed unused vars must match /^_/u
} from "fastify-zod-openapi";
import { ts, withTags } from "api/components/index.js";

Expand Down Expand Up @@ -108,6 +108,7 @@
.openapi({ description: "Event host filter." }),
ts,
}),
summary: "Retrieve calendar events with applied filters.",
// response: { 200: getEventsSchema },
}),
},
Expand Down Expand Up @@ -235,6 +236,7 @@
example: "6667e095-8b04-4877-b361-f636f459ba42",
}),
}),
summary: "Modify a calendar event.",
}) satisfies FastifyZodOpenApiSchema,
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.EVENTS_MANAGER]);
Expand Down Expand Up @@ -372,6 +374,7 @@
// resource: z.string(),
// }),
// },
summary: "Delete a calendar event.",
}) satisfies FastifyZodOpenApiSchema,
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.EVENTS_MANAGER]);
Expand Down Expand Up @@ -440,6 +443,7 @@
querystring: z.object({
ts,
}),
summary: "Retrieve a calendar event.",
// response: { 200: getEventSchema },
}),
},
Expand Down
91 changes: 51 additions & 40 deletions src/api/routes/iam.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FastifyPluginAsync } from "fastify";
import { AppRoles } from "../../common/roles.js";
import { zodToJsonSchema } from "zod-to-json-schema";

Check warning on line 3 in src/api/routes/iam.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'zodToJsonSchema' is defined but never used. Allowed unused vars must match /^_/u
import {
addToTenant,
getEntraIdToken,
Expand All @@ -23,10 +23,10 @@
import {
invitePostRequestSchema,
groupMappingCreatePostSchema,
entraActionResponseSchema,

Check warning on line 26 in src/api/routes/iam.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'entraActionResponseSchema' is defined but never used. Allowed unused vars must match /^_/u
groupModificationPatchSchema,
EntraGroupActions,
entraGroupMembershipListResponse,

Check warning on line 29 in src/api/routes/iam.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'entraGroupMembershipListResponse' is defined but never used. Allowed unused vars must match /^_/u
entraProfilePatchRequest,
} from "../../common/types/iam.js";
import {
Expand All @@ -37,11 +37,11 @@
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
import { createAuditLogEntry } from "api/functions/auditLog.js";
import { Modules } from "common/modules.js";
import { groupId, withTags } from "api/components/index.js";
import { groupId, withRoles, withTags } from "api/components/index.js";
import {
FastifyZodOpenApiTypeProvider,
serializerCompiler,

Check warning on line 43 in src/api/routes/iam.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'serializerCompiler' is defined but never used. Allowed unused vars must match /^_/u
validatorCompiler,

Check warning on line 44 in src/api/routes/iam.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'validatorCompiler' is defined but never used. Allowed unused vars must match /^_/u
} from "fastify-zod-openapi";
import { z } from "zod";

Expand Down Expand Up @@ -81,6 +81,7 @@
{
schema: withTags(["IAM"], {
body: entraProfilePatchRequest,
summary: "Update user's profile.",
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, []);
Expand All @@ -103,20 +104,22 @@
userOid,
request.body,
);
reply.status(201);
reply.status(201).send();
},
);
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().get(
"/groups/:groupId/roles",
{
schema: withTags(["IAM"], {
params: z.object({
groupId,
schema: withRoles(
[AppRoles.IAM_ADMIN],
withTags(["IAM"], {
params: z.object({
groupId,
}),
summary: "Get a group's application role mappings.",
}),
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.IAM_ADMIN]);
},
),
onRequest: fastify.authorizeFromSchema,
},
async (request, reply) => {
try {
Expand All @@ -142,15 +145,17 @@
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().post(
"/groups/:groupId/roles",
{
schema: withTags(["IAM"], {
params: z.object({
groupId,
schema: withRoles(
[AppRoles.IAM_ADMIN],
withTags(["IAM"], {
params: z.object({
groupId,
}),
body: groupMappingCreatePostSchema,
summary: "Update a group's application role mappings.",
}),
body: groupMappingCreatePostSchema,
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.IAM_ADMIN]);
},
),
onRequest: fastify.authorizeFromSchema,
},
async (request, reply) => {
const groupId = (request.params as Record<string, string>).groupId;
Expand Down Expand Up @@ -198,13 +203,15 @@
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().post(
"/inviteUsers",
{
schema: withTags(["IAM"], {
body: invitePostRequestSchema,
// response: { 202: entraActionResponseSchema },
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.IAM_INVITE_ONLY]);
},
schema: withRoles(
[AppRoles.IAM_INVITE_ONLY, AppRoles.IAM_ADMIN],
withTags(["IAM"], {
body: invitePostRequestSchema,
summary: "Invite a user to the ACM @ UIUC Entra ID tenant.",
// response: { 202: entraActionResponseSchema },
}),
),
onRequest: fastify.authorizeFromSchema,
},
async (request, reply) => {
const emails = request.body.emails;
Expand Down Expand Up @@ -274,15 +281,17 @@
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().patch(
"/groups/:groupId",
{
schema: withTags(["IAM"], {
params: z.object({
groupId,
schema: withRoles(
[AppRoles.IAM_ADMIN],
withTags(["IAM"], {
params: z.object({
groupId,
}),
body: groupModificationPatchSchema,
summary: "Update the members of a group.",
}),
body: groupModificationPatchSchema,
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.IAM_ADMIN]);
},
),
onRequest: fastify.authorizeFromSchema,
},
async (request, reply) => {
const groupId = (request.params as Record<string, string>).groupId;
Expand Down Expand Up @@ -421,15 +430,17 @@
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().get(
"/groups/:groupId",
{
schema: withTags(["IAM"], {
// response: { 200: entraGroupMembershipListResponse },
params: z.object({
groupId,
schema: withRoles(
[AppRoles.IAM_ADMIN],
withTags(["IAM"], {
// response: { 200: entraGroupMembershipListResponse },
params: z.object({
groupId,
}),
summary: "Get the members of a group.",
}),
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.IAM_ADMIN]);
},
),
onRequest: fastify.authorizeFromSchema,
},
async (request, reply) => {
const groupId = (request.params as Record<string, string>).groupId;
Expand Down
2 changes: 2 additions & 0 deletions src/api/routes/ics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const icalPlugin: FastifyPluginAsync = async (fastify, _options) => {
.optional(z.enum(OrganizationList as [string, ...string[]]))
.openapi({ description: "Host to get calendar for." }),
}),
summary:
"Retrieve the calendar for ACM @ UIUC or a specific sub-organization.",
} satisfies FastifyZodOpenApiSchema),
},
async (request, reply) => {
Expand Down
51 changes: 27 additions & 24 deletions src/api/routes/logs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { QueryCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
import { withTags } from "api/components/index.js";
import { withRoles, withTags } from "api/components/index.js";
import { createAuditLogEntry } from "api/functions/auditLog.js";
import rateLimiter from "api/plugins/rateLimiter.js";
import { genericConfig } from "common/config.js";
Expand Down Expand Up @@ -33,32 +33,35 @@ const logsPlugin: FastifyPluginAsync = async (fastify, _options) => {
fastify.withTypeProvider<FastifyZodOpenApiTypeProvider>().get(
"/:module",
{
schema: withTags(["Logging"], {
querystring: z
.object({
start: z.coerce.number().openapi({
description: "Epoch timestamp for the start of the search range",
example: 1745114772,
schema: withRoles(
[AppRoles.AUDIT_LOG_VIEWER],
withTags(["Logging"], {
querystring: z
.object({
start: z.coerce.number().openapi({
description:
"Epoch timestamp for the start of the search range",
example: 1745114772,
}),
end: z.coerce.number().openapi({
description: "Epoch timestamp for the end of the search range",
example: 1745201172,
}),
})
.refine((data) => data.start <= data.end, {
message: "Start time must be less than or equal to end time",
path: ["start"],
}),
end: z.coerce.number().openapi({
description: "Epoch timestamp for the end of the search range",
example: 1745201172,
}),
})
.refine((data) => data.start <= data.end, {
message: "Start time must be less than or equal to end time",
path: ["start"],
params: z.object({
module: z
.nativeEnum(Modules)
.openapi({ description: "Module to get audit logs for." }),
}),
params: z.object({
module: z
.nativeEnum(Modules)
.openapi({ description: "Module to get audit logs for." }),
summary: "Retrieve audit logs for a module.",
// response: { 200: responseSchema },
}),
// response: { 200: responseSchema },
}),
onRequest: async (request, reply) => {
await fastify.authorize(request, reply, [AppRoles.AUDIT_LOG_VIEWER]);
},
),
onRequest: fastify.authorizeFromSchema,
},
async (request, reply) => {
const { module } = request.params;
Expand Down
3 changes: 1 addition & 2 deletions src/api/routes/mobileWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ const mobileWalletRoute: FastifyPluginAsync = async (fastify, _options) => {
message: "Email must be on the illinois.edu domain.",
path: ["email"],
}),
summary:
"Retrieve mobile wallet passes for various ACM @ UIUC-issued tickets/services.",
summary: "Email mobile wallet pass for ACM membership to user.",
}),
},
async (request, reply) => {
Expand Down
8 changes: 6 additions & 2 deletions src/api/routes/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ const organizationsPlugin: FastifyPluginAsync = async (fastify, _options) => {
});
fastify.get(
"",
{ schema: withTags(["Generic"], {}) },
async (request, reply) => {
{
schema: withTags(["Generic"], {
summary: "Get a list of ACM @ UIUC sub-organizations.",
}),
},
async (_request, reply) => {
reply.send(OrganizationList);
},
);
Expand Down
6 changes: 5 additions & 1 deletion src/api/routes/protected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ const protectedRoute: FastifyPluginAsync = async (fastify, _options) => {
});
fastify.get(
"",
{ schema: withTags(["Generic"], {}) },
{
schema: withTags(["Generic"], {
summary: "Get a user's username and roles.",
}),
},
async (request, reply) => {
const roles = await fastify.authorize(request, reply, []);
reply.send({ username: request.username, roles: Array.from(roles) });
Expand Down
Loading