Skip to content

Commit

Permalink
Merge branch 'main' into eeen17/lead-screen
Browse files Browse the repository at this point in the history
* kinda messed up rebase, so merging to clean it all up, won't touch rebase after pushing to remote again lol
  • Loading branch information
eeen17 committed Feb 7, 2025
2 parents 7918366 + 2781411 commit f924be4
Show file tree
Hide file tree
Showing 51 changed files with 2,437 additions and 2,294 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ jobs:
run: make dev_health_check
- name: Run live testing
run: make test_live_integration
env:
JWT_KEY: ${{ secrets.JWT_KEY }}
- name: Run E2E testing
run: make test_e2e
env:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ jobs:
python-version: 3.11
- name: Run live testing
run: make test_live_integration
env:
JWT_KEY: ${{ secrets.JWT_KEY }}
- name: Run E2E testing
run: make test_e2e
env:
Expand Down
2 changes: 2 additions & 0 deletions cloudformation/iam.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ Resources:
- !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-iam-userroles/*
- !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-iam-grouproles
- !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-iam-grouproles/*
- !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-stripe-links
- !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-stripe-links/*

PolicyName: lambda-dynamo
Outputs:
Expand Down
28 changes: 28 additions & 0 deletions cloudformation/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,34 @@ Resources:
Projection:
ProjectionType: ALL

StripeLinksTable:
Type: 'AWS::DynamoDB::Table'
DeletionPolicy: "Retain"
UpdateReplacePolicy: "Retain"
Properties:
BillingMode: 'PAY_PER_REQUEST'
TableName: infra-core-api-stripe-links
DeletionProtectionEnabled: true
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: false
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
- AttributeName: linkId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: "HASH"
- AttributeName: linkId
KeyType: "RANGE"
GlobalSecondaryIndexes:
- IndexName: LinkIdIndex
KeySchema:
- AttributeName: linkId
KeyType: "HASH"
Projection:
ProjectionType: "ALL"

CacheRecordsTable:
Type: 'AWS::DynamoDB::Table'
DeletionPolicy: "Retain"
Expand Down
58 changes: 29 additions & 29 deletions generate_jwt.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
import jwt from 'jsonwebtoken';
import jwt from "jsonwebtoken";
import * as dotenv from "dotenv";
dotenv.config();

const username = process.env.JWTGEN_USERNAME || '[email protected]'
const username = process.env.JWTGEN_USERNAME || "[email protected]"
const payload = {
aud: "custom_jwt",
iss: "custom_jwt",
iat: Math.floor(Date.now() / 1000),
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (3600 * 24), // Token expires after 24 hour
acr: "1",
aio: "AXQAi/8TAAAA",
amr: ["pwd"],
appid: "your-app-id",
appidacr: "1",
email: username,
groups: ["0"],
idp: "https://login.microsoftonline.com",
ipaddr: "192.168.1.1",
name: "Doe, John",
oid: "00000000-0000-0000-0000-000000000000",
rh: "rh-value",
scp: "user_impersonation",
sub: "subject",
tid: "tenant-id",
unique_name: username,
uti: "uti-value",
ver: "1.0"
aud: "custom_jwt",
iss: "custom_jwt",
iat: Math.floor(Date.now() / 1000),
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600 * 24, // Token expires after 24 hour
acr: "1",
aio: "AXQAi/8TAAAA",
amr: ["pwd"],
appid: "your-app-id",
appidacr: "1",
email: username,
groups: ["0"],
idp: "https://login.microsoftonline.com",
ipaddr: "192.168.1.1",
name: "Doe, John",
oid: "00000000-0000-0000-0000-000000000000",
rh: "rh-value",
scp: "user_impersonation",
sub: "subject",
tid: "tenant-id",
unique_name: username,
uti: "uti-value",
ver: "1.0",
};

const secretKey = process.env.JwtSigningKey;
const token = jwt.sign(payload, secretKey, { algorithm: 'HS256' });
console.log(`USERNAME=${username}`)
console.log('=====================')
console.log(token)
const token = jwt.sign(payload, secretKey, { algorithm: "HS256" });
console.log(`USERNAME=${username}`);
console.log("=====================");
console.log(token);
6 changes: 1 addition & 5 deletions src/api/functions/authorization.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
DynamoDBClient,
GetItemCommand,
QueryCommand,
} from "@aws-sdk/client-dynamodb";
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
import { genericConfig } from "../../common/config.js";
import { DatabaseFetchError } from "../../common/errors/index.js";
Expand Down
50 changes: 48 additions & 2 deletions src/api/functions/entraId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
officersGroupTestingId,
} from "../../common/config.js";
import {
BaseError,
EntraFetchError,
EntraGroupError,
EntraInvitationError,
EntraPatchError,
InternalServerError,
} from "../../common/errors/index.js";
import { getSecretValue } from "../plugins/auth.js";
Expand All @@ -18,8 +18,8 @@ import { getItemFromCache, insertItemIntoCache } from "./cache.js";
import {
EntraGroupActions,
EntraInvitationResponse,
ProfilePatchRequest,
} from "../../common/types/iam.js";
import { FastifyInstance } from "fastify";
import { UserProfileDataBase } from "common/types/msGraphApi.js";
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
Expand Down Expand Up @@ -397,3 +397,49 @@ export async function getUserProfile(
});
}
}

/**
* Patches the profile of a user from Entra ID.
* @param token - Entra ID token authorized to perform this action.
* @param userId - The user ID to patch the profile for.
* @throws {EntraUserError} If setting the user profile fails.
* @returns {Promise<void>} nothing
*/
export async function patchUserProfile(
token: string,
email: string,
userId: string,
data: ProfilePatchRequest,
): Promise<void> {
try {
const url = `https://graph.microsoft.com/v1.0/users/${userId}`;
const response = await fetch(url, {
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});

if (!response.ok) {
const errorData = (await response.json()) as {
error?: { message?: string };
};
throw new EntraPatchError({
message: errorData?.error?.message ?? response.statusText,
email,
});
}
return;
} catch (error) {
if (error instanceof EntraPatchError) {
throw error;
}

throw new EntraPatchError({
message: error instanceof Error ? error.message : String(error),
email,
});
}
}
10 changes: 8 additions & 2 deletions src/api/functions/membership.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FastifyBaseLogger, FastifyInstance } from "fastify";
import { FastifyBaseLogger } from "fastify";

export async function checkPaidMembership(
endpoint: string,
Expand All @@ -11,7 +11,13 @@ export async function checkPaidMembership(
log.trace(`Got Membership API Payload for ${netId}: ${membershipApiPayload}`);
try {
return membershipApiPayload["isPaidMember"];
} catch (e: any) {
} catch (e: unknown) {
if (!(e instanceof Error)) {
log.error(
"Failed to get response from membership API (unknown error type.)",
);
throw e;
}
log.error(`Failed to get response from membership API: ${e.toString()}`);
throw e;
}
Expand Down
3 changes: 0 additions & 3 deletions src/api/functions/mobileWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import { getSecretValue } from "../plugins/auth.js";
import {
ConfigType,
genericConfig,
GenericConfigType,
SecretConfig,
} from "../../common/config.js";
import {
InternalServerError,
UnauthorizedError,
} from "../../common/errors/index.js";
import { FastifyInstance, FastifyRequest } from "fastify";
// these make sure that esbuild includes the files
import icon from "../resources/MembershipPass.pkpass/icon.png";
import logo from "../resources/MembershipPass.pkpass/logo.png";
import strip from "../resources/MembershipPass.pkpass/strip.png";
Expand Down
55 changes: 55 additions & 0 deletions src/api/functions/stripe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Stripe from "stripe";

export type StripeLinkCreateParams = {
invoiceId: string;
invoiceAmountUsd: number;
contactName: string;
contactEmail: string;
createdBy: string;
stripeApiKey: string;
};

/**
* Create a Stripe payment link for an invoice. Note that invoiceAmountUsd MUST IN CENTS!!
* @param {StripeLinkCreateParams} options
* @returns {string} A stripe link that can be used to pay the invoice
*/
export const createStripeLink = async ({
invoiceId,
invoiceAmountUsd,
contactName,
contactEmail,
createdBy,
stripeApiKey,
}: StripeLinkCreateParams): Promise<{
linkId: string;
priceId: string;
productId: string;
url: string;
}> => {
const stripe = new Stripe(stripeApiKey);
const description = `Created for ${contactName} (${contactEmail}) by ${createdBy}.`;
const product = await stripe.products.create({
name: `Payment for Invoice: ${invoiceId}`,
description,
});
const price = await stripe.prices.create({
currency: "usd",
unit_amount: invoiceAmountUsd,
product: product.id,
});
const paymentLink = await stripe.paymentLinks.create({
line_items: [
{
price: price.id,
quantity: 1,
},
],
});
return {
url: paymentLink.url,
linkId: paymentLink.id,
productId: product.id,
priceId: price.id,
};
};
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import NodeCache from "node-cache";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
import mobileWalletRoute from "./routes/mobileWallet.js";
import stripeRoutes from "./routes/stripe.js";

dotenv.config();

Expand Down Expand Up @@ -113,6 +114,7 @@ async function init() {
api.register(iamRoutes, { prefix: "/iam" });
api.register(ticketsPlugin, { prefix: "/tickets" });
api.register(mobileWalletRoute, { prefix: "/mobileWallet" });
api.register(stripeRoutes, { prefix: "/stripe" });
if (app.runEnvironment === "dev") {
api.register(vendingPlugin, { prefix: "/vending" });
}
Expand Down
1 change: 1 addition & 0 deletions src/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"passkit-generator": "^3.3.1",
"pino": "^9.6.0",
"pluralize": "^8.0.0",
"stripe": "^17.6.0",
"uuid": "^11.0.5",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.2",
Expand Down
8 changes: 6 additions & 2 deletions src/api/plugins/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from "../../common/errors/index.js";
import { genericConfig, SecretConfig } from "../../common/config.js";
import { getGroupRoles, getUserRoles } from "../functions/authorization.js";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";

function intersection<T>(setA: Set<T>, setB: Set<T>): Set<T> {
const _intersection = new Set<T>();
Expand Down Expand Up @@ -49,6 +48,7 @@ export type AadToken = {
sub: string;
tid: string;
unique_name: string;
upn?: string;
uti: string;
ver: string;
roles?: string[];
Expand Down Expand Up @@ -160,7 +160,10 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
verifyOptions,
) as AadToken;
request.tokenPayload = verifiedTokenData;
request.username = verifiedTokenData.email || verifiedTokenData.sub;
request.username =
verifiedTokenData.email ||
verifiedTokenData.upn?.replace("acm.illinois.edu", "illinois.edu") ||
verifiedTokenData.sub;
const expectedRoles = new Set(validRoles);
if (verifiedTokenData.groups) {
const groupRoles = await Promise.allSettled(
Expand Down Expand Up @@ -237,6 +240,7 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
});
}
request.log.info(`authenticated request from ${request.username} `);
request.userRoles = userRoles;
return userRoles;
},
);
Expand Down
2 changes: 2 additions & 0 deletions src/api/resources/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

declare module "*.png" {
const value: string;
export default value;
Expand Down
Loading

0 comments on commit f924be4

Please sign in to comment.