Skip to content

Mobile Wallet PR #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 29, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,4 @@ __pycache__
/playwright-report/
/blob-report/
/playwright/.cache/
dist_devel/
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ clean:
rm -rf src/ui/node_modules/
rm -rf dist/
rm -rf dist_ui/
rm -rf dist_devel/

build: src/ cloudformation/ docs/
yarn -D
VITE_BUILD_HASH=$(GIT_HASH) yarn build
cp -r src/api/resources/ dist/api/resources
sam build --template-file cloudformation/main.yml

local:
Expand Down
19 changes: 18 additions & 1 deletion cloudformation/iam.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Parameters:
LambdaFunctionName:
Type: String
AllowedPattern: ^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$
SesEmailDomain:
Type: String
Resources:
ApiLambdaIAMRole:
Type: AWS::IAM::Role
Expand All @@ -24,6 +26,21 @@ Resources:
Service:
- lambda.amazonaws.com
Policies:
- PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- ses:SendEmail
- ses:SendRawEmail
Effect: Allow
Resource: "*"
Condition:
StringEquals:
ses:FromAddress: !Sub "membership@${SesEmailDomain}"
ForAllValues:StringLike:
ses:Recipients:
- "*@illinois.edu"
PolicyName: ses-membership
- PolicyDocument:
Version: '2012-10-17'
Statement:
Expand Down Expand Up @@ -85,4 +102,4 @@ Outputs:
Value:
Fn::GetAtt:
- ApiLambdaIAMRole
- Arn
- Arn
25 changes: 4 additions & 21 deletions cloudformation/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ Mappings:
General:
dev:
LogRetentionDays: 7
SesDomain: "aws.qa.acmuiuc.org"
prod:
LogRetentionDays: 365
SesDomain: "acm.illinois.edu"
ApiGwConfig:
dev:
ApiCertificateArn: arn:aws:acm:us-east-1:427040638965:certificate/63ccdf0b-d2b5-44f0-b589-eceffb935c23
Expand Down Expand Up @@ -71,6 +73,7 @@ Resources:
Parameters:
RunEnvironment: !Ref RunEnvironment
LambdaFunctionName: !Sub ${ApplicationPrefix}-lambda
SesEmailDomain: !FindInMap [General, !Ref RunEnvironment, SesDomain]

AppLogGroups:
Type: AWS::Serverless::Application
Expand Down Expand Up @@ -120,29 +123,9 @@ Resources:
Type: AWS::Serverless::Function
DependsOn:
- AppLogGroups
Metadata:
BuildMethod: esbuild
BuildProperties:
Format: esm
Minify: true
OutExtension:
- .js=.mjs
Target: "es2022"
Sourcemap: false
EntryPoints:
- api/lambda.js
External:
- aws-sdk
Banner:
- js=import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire as topLevelCreateRequire } from 'module';
const require = topLevelCreateRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Properties:
Architectures: [arm64]
CodeUri: ../dist
CodeUri: ../dist/lambda
AutoPublishAlias: live
Runtime: nodejs22.x
Description: !Sub "${ApplicationFriendlyName} API Lambda"
Expand Down
2 changes: 1 addition & 1 deletion generate_jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const payload = {
groups: ["0"],
idp: "https://login.microsoftonline.com",
ipaddr: "192.168.1.1",
name: "John Doe",
name: "Doe, John",
oid: "00000000-0000-0000-0000-000000000000",
rh: "rh-value",
scp: "user_impersonation",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"scripts": {
"build": "yarn workspaces run build && yarn lockfile-manage",
"dev": "concurrently --names 'api,ui' 'yarn workspace infra-core-api run dev' 'yarn workspace infra-core-ui run dev'",
"lockfile-manage": "synp --with-workspace --source-file yarn.lock && cp package-lock.json dist/ && cp src/api/package.json dist/ && rm package-lock.json",
"lockfile-manage": "synp --with-workspace --source-file yarn.lock && cp package-lock.json dist/lambda/ && cp src/api/package.lambda.json dist/lambda/package.json && rm package-lock.json",
"prettier": "yarn workspaces run prettier && prettier --check tests/**/*.ts",
"prettier:write": "yarn workspaces run prettier:write && prettier --write tests/**/*.ts",
"lint": "yarn workspaces run lint",
Expand Down
39 changes: 39 additions & 0 deletions src/api/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import esbuild from "esbuild";
import { resolve } from "path";

esbuild
.build({
entryPoints: ["api/lambda.js"], // Entry file
bundle: true,
format: "esm",
minify: true,
outdir: "../../dist/lambda/",
outExtension: { ".js": ".mjs" },
loader: {
".png": "file",
".pkpass": "file",
".json": "file",
}, // File loaders
target: "es2022", // Target ES2022
sourcemap: false,
platform: "node",
external: ["aws-sdk", "moment-timezone", "passkit-generator", "fastify"],
alias: {
'moment-timezone': resolve(process.cwd(), '../../node_modules/moment-timezone/builds/moment-timezone-with-data-10-year-range.js')
},
banner: {
js: `
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire as topLevelCreateRequire } from 'module';
const require = topLevelCreateRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
`.trim(),
}, // Banner for compatibility with CommonJS
})
.then(() => console.log("Build completed successfully!"))
.catch((error) => {
console.error("Build failed:", error);
process.exit(1);
});
47 changes: 47 additions & 0 deletions src/api/esbuild.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { build, context } from 'esbuild';
import { readFileSync } from 'fs';
import { resolve } from 'path';

const isWatching = !!process.argv.includes('--watch')
const nodePackage = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8'));

const buildOptions = {
entryPoints: [resolve(process.cwd(), 'index.ts')],
outfile: resolve(process.cwd(), '../', '../', 'dist_devel', 'index.js'),
bundle: true,
platform: 'node',
format: 'esm',
external: [
Object.keys(nodePackage.dependencies ?? {}),
Object.keys(nodePackage.peerDependencies ?? {}),
Object.keys(nodePackage.devDependencies ?? {}),
].flat(),
loader: {
'.png': 'file', // Add this line to specify a loader for .png files
},
alias: {
'moment-timezone': resolve(process.cwd(), '../../node_modules/moment-timezone/builds/moment-timezone-with-data-10-year-range.js')
},
banner: {
js: `
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire as topLevelCreateRequire } from 'module';
const require = topLevelCreateRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
`.trim(),
}, // Banner for compatibility with CommonJS
};

if (isWatching) {
context(buildOptions).then(ctx => {
if (isWatching) {
ctx.watch();
} else {
ctx.rebuild();
}
});
} else {
build(buildOptions)
}
46 changes: 46 additions & 0 deletions src/api/functions/entraId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
officersGroupTestingId,
} from "../../common/config.js";
import {
BaseError,

Check warning on line 9 in src/api/functions/entraId.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

'BaseError' is defined but never used. Allowed unused vars must match /^_/u
EntraFetchError,
EntraGroupError,
EntraInvitationError,
InternalServerError,
Expand All @@ -19,6 +20,7 @@
EntraInvitationResponse,
} from "../../common/types/iam.js";
import { FastifyInstance } from "fastify";
import { UserProfileDataBase } from "common/types/msGraphApi.js";

function validateGroupId(groupId: string): boolean {
const groupIdPattern = /^[a-zA-Z0-9-]+$/; // Adjust the pattern as needed
Expand Down Expand Up @@ -351,3 +353,47 @@
});
}
}

/**
* Retrieves the profile of a user from Entra ID.
* @param token - Entra ID token authorized to perform this action.
* @param userId - The user ID to fetch the profile for.
* @throws {EntraUserError} If fetching the user profile fails.
* @returns {Promise<UserProfileDataBase>} The user's profile information.
*/
export async function getUserProfile(
token: string,
email: string,
): Promise<UserProfileDataBase> {
const userId = await resolveEmailToOid(token, email);
try {
const url = `https://graph.microsoft.com/v1.0/users/${userId}?$select=userPrincipalName,givenName,surname,displayName,otherMails,mail`;
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});

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

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

Check warning on line 1 in src/api/functions/membership.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

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

export async function checkPaidMembership(
endpoint: string,
log: FastifyBaseLogger,
netId: string,
) {
const membershipApiPayload = (await (
await fetch(`${endpoint}?netId=${netId}`)
).json()) as { netId: string; isPaidMember: boolean };
log.trace(`Got Membership API Payload for ${netId}: ${membershipApiPayload}`);
try {
return membershipApiPayload["isPaidMember"];
} catch (e: any) {

Check warning on line 14 in src/api/functions/membership.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

Unexpected any. Specify a different type
log.error(`Failed to get response from membership API: ${e.toString()}`);
throw e;
}
}
Loading
Loading