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
2 changes: 2 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Run health check
run: make dev_health_check
- name: Run live testing
run: make test_live_integration
18 changes: 17 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
yarn lint --fix
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Get all staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)

if [ -n "$STAGED_FILES" ]; then
echo "Running lint with fix on staged files..."
# Run lint on all files (modifies files in the working directory)
yarn lint --fix

echo "Re-adding originally staged files to the staging area..."
# Re-add only the originally staged files
echo "$STAGED_FILES" | xargs git add
else
echo "No staged files to process."
fi
55 changes: 48 additions & 7 deletions src/api/functions/entraId.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { genericConfig } from "../../common/config.js";
import {
execCouncilGroupId,
execCouncilTestingGroupId,
genericConfig,
officersGroupId,
officersGroupTestingId,
} from "../../common/config.js";
import {
BaseError,
EntraGroupError,
EntraInvitationError,
InternalServerError,
Expand Down Expand Up @@ -190,7 +197,32 @@ export async function modifyGroup(
message: "User's domain must be illinois.edu to be added to the group.",
});
}

// if adding to exec group, check that all exec members we want to add are paid members
const paidMemberRequiredGroups = [
execCouncilGroupId,
execCouncilTestingGroupId,
officersGroupId,
officersGroupTestingId,
];
if (
paidMemberRequiredGroups.includes(group) &&
action === EntraGroupActions.ADD
) {
const netId = email.split("@")[0];
const response = await fetch(
`https://membership.acm.illinois.edu/api/v1/checkMembership?netId=${netId}`,
);
const membershipStatus = (await response.json()) as {
netId: string;
isPaidMember: boolean;
};
if (!membershipStatus["isPaidMember"]) {
throw new EntraGroupError({
message: `${netId} is not a paid member. This group requires that all members are paid members.`,
group,
});
}
}
try {
const oid = await resolveEmailToOid(token, email);
const methodMapper = {
Expand Down Expand Up @@ -220,6 +252,12 @@ export async function modifyGroup(
const errorData = (await response.json()) as {
error?: { message?: string };
};
if (
errorData?.error?.message ===
"One or more added object references already exist for the following modified properties: 'members'."
) {
return true;
}
throw new EntraGroupError({
message: errorData?.error?.message ?? response.statusText,
group,
Expand All @@ -231,12 +269,15 @@ export async function modifyGroup(
if (error instanceof EntraGroupError) {
throw error;
}

throw new EntraGroupError({
message: error instanceof Error ? error.message : String(error),
group,
});
const message = error instanceof Error ? error.message : String(error);
if (message) {
throw new EntraGroupError({
message,
group,
});
}
}
return false;
}

/**
Expand Down
24 changes: 19 additions & 5 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ import { InternalServerError } from "../common/errors/index.js";
import eventsPlugin from "./routes/events.js";
import cors from "@fastify/cors";
import fastifyZodValidationPlugin from "./plugins/validate.js";
import { environmentConfig } from "../common/config.js";
import { environmentConfig, genericConfig } from "../common/config.js";
import organizationsPlugin from "./routes/organizations.js";
import icalPlugin from "./routes/ics.js";
import vendingPlugin from "./routes/vending.js";
import * as dotenv from "dotenv";
import iamRoutes from "./routes/iam.js";
import ticketsPlugin from "./routes/tickets.js";
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";

dotenv.config();

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

async function init() {
const app: FastifyInstance = fastify({
logger: true,
logger: {
level: process.env.LOG_LEVEL || "info",
},
disableRequestLogging: true,
genReqId: (request) => {
const header = request.headers["x-apigateway-event"];
Expand Down Expand Up @@ -90,12 +94,22 @@ async function init() {
}

if (import.meta.url === `file://${process.argv[1]}`) {
// local development
console.log(`Logging level set to ${process.env.LOG_LEVEL || "info"}`);
const client = new STSClient({ region: genericConfig.AwsRegion });
const command = new GetCallerIdentityCommand({});
try {
const data = await client.send(command);
console.log(`Logged in to AWS as ${data.Arn} on account ${data.Account}.`);
} catch {
console.error(
`Could not get AWS STS credentials: are you logged in to AWS? Run "aws configure sso" to log in.`,
);
process.exit(1);
}
const app = await init();
app.listen({ port: 8080 }, (err) => {
app.listen({ port: 8080 }, async (err) => {
/* eslint no-console: ["error", {"allow": ["log", "error"]}] */
if (err) console.error(err);
console.log("Server listening on 8080");
});
}
export default init;
7 changes: 5 additions & 2 deletions src/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
"type": "module",
"scripts": {
"build": "tsc",
"dev": "tsx watch index.ts",
"dev": "cross-env LOG_LEVEL=debug tsx watch index.ts",
"typecheck": "tsc --noEmit",
"lint": "eslint . --ext .ts --cache",
"prettier": "prettier --check *.ts **/*.ts",
"prettier:write": "prettier --write *.ts **/*.ts"
},
"dependencies": {
"@aws-sdk/client-sts": "^3.726.0"
}
}
}
7 changes: 6 additions & 1 deletion src/api/routes/iam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ import {
EntraInvitationError,
InternalServerError,
NotFoundError,
ValidationError,
} from "../../common/errors/index.js";
import {
DynamoDBClient,
GetItemCommand,
PutItemCommand,
} from "@aws-sdk/client-dynamodb";
import { genericConfig } from "../../common/config.js";
import {
execCouncilGroupId,
execCouncilTestingGroupId,
genericConfig,
} from "../../common/config.js";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
import {
InviteUserPostRequest,
Expand Down
4 changes: 3 additions & 1 deletion src/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ type EnvironmentConfigType = {

export const infraChairsGroupId = "48591dbc-cdcb-4544-9f63-e6b92b067e33";
export const officersGroupId = "ff49e948-4587-416b-8224-65147540d5fc";
export const officersGroupTestingId = "0e6e9199-506f-4ede-9d1b-e73f6811c9e5";
export const execCouncilGroupId = "ad81254b-4eeb-4c96-8191-3acdce9194b1";
export const execCouncilTestingGroupId = "dbe18eb2-9675-46c4-b1ef-749a6db4fedd";

const genericConfig: GenericConfigType = {
EventsDynamoTableName: "infra-core-api-events",
Expand Down Expand Up @@ -109,7 +111,7 @@ const environmentConfig: EnvironmentConfigType = {
/^https:\/\/(?:.*\.)?acmuiuc\.pages\.dev$/,
],
AadValidClientId: "5e08cf0f-53bb-4e09-9df2-e9bdc3467296",
},
}
};

export type SecretConfig = {
Expand Down
14 changes: 14 additions & 0 deletions src/ui/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { execCouncilGroupId, execCouncilTestingGroupId } from '@common/config';

export const runEnvironments = ['dev', 'prod', 'local-dev'] as const;
// local dev should be used when you want to test against a local instance of the API

Expand All @@ -9,6 +11,9 @@ export type ValidService = ValidServices;
export type ConfigType = {
AadValidClientId: string;
ServiceConfiguration: Record<ValidServices, ServiceConfiguration>;
KnownGroupMappings: {
Exec: string;
};
};

export type ServiceConfiguration = {
Expand Down Expand Up @@ -45,6 +50,9 @@ const environmentConfig: EnvironmentConfigType = {
baseEndpoint: 'https://merchapi.acm.illinois.edu',
},
},
KnownGroupMappings: {
Exec: execCouncilTestingGroupId,
},
},
dev: {
AadValidClientId: 'd1978c23-6455-426a-be4d-528b2d2e4026',
Expand All @@ -65,6 +73,9 @@ const environmentConfig: EnvironmentConfigType = {
baseEndpoint: 'https://merchapi.acm.illinois.edu',
},
},
KnownGroupMappings: {
Exec: execCouncilTestingGroupId,
},
},
prod: {
AadValidClientId: '43fee67e-e383-4071-9233-ef33110e9386',
Expand All @@ -85,6 +96,9 @@ const environmentConfig: EnvironmentConfigType = {
baseEndpoint: 'https://merchapi.acm.illinois.edu',
},
},
KnownGroupMappings: {
Exec: execCouncilGroupId,
},
},
} as const;

Expand Down
Loading
Loading