Skip to content

Commit

Permalink
Merge pull request #1559 from Enterprise-CMCS/develop
Browse files Browse the repository at this point in the history
merge sub ld
  • Loading branch information
Dark-Knight-1313 authored Dec 13, 2024
2 parents 83e17ae + 6696a62 commit 98474e1
Show file tree
Hide file tree
Showing 142 changed files with 2,758 additions and 243 deletions.
1 change: 1 addition & 0 deletions .github/build_vars.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var_list=(
COGNITO_TEST_USERS_PASSWORD
NO_EMAIL_DEBUG
REACT_APP_GOOGLE_TAG
REACT_APP_LD_CLIENT_ID
)

set_value() {
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jobs:
name: ${{ startsWith(github.ref_name, 'snyk-') && 'snyk' || github.ref_name }}
url: "https://onemac.cms.gov"
steps:
- name: Check GITHUB_REF
run: echo "GITHUB_REF is $GITHUB_REF"
- name: set branch_name
run: echo "branch_name=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
- name: Check branch name is a legal serverless stage name
Expand Down Expand Up @@ -110,6 +112,11 @@ jobs:
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Fetch SSM Parameter
id: fetch_ssm
run: |
LdClientId=$(aws ssm get-parameter --name "/configuration/onemacmmdl/LdClientId" --query "Parameter.Value" --output text)
echo "REACT_APP_LD_CLIENT_ID=${LdClientId}" >> $GITHUB_ENV
- uses: actions/setup-node@v3
with:
node-version: "20.x"
Expand Down Expand Up @@ -312,7 +319,7 @@ jobs:
Profile_View_CMS_User_Denied.spec.feature,
Profile_View_CMS_User_Revoked.spec.feature,
Profile_View_Helpdesk_User.spec.feature,
Profile_View_Mixed_Case_Emails.spec.feature,
Profile_View_Remove_Email_From_URL.spec.feature,
Profile_View_State_Submitter.spec.feature,
Profile_View_State_System_Admin.spec.feature,
Request_A_Role_Change_As_CMS_Read_Only.spec.feature,
Expand Down
25 changes: 25 additions & 0 deletions services/admin/handlers/addRolesToJWT.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getUser } from "../../app-api/getUser";

const handler = async (event) => {
console.log("JWT claims before modification:", JSON.stringify(event));
try{
const userEmail = event.request.userAttributes.email;
const user = await getUser(userEmail);
const roles = [];
for (const role of user.roleList) {
roles.push(role.role)
}
event.response = event.response || {};
event.response.claimsOverrideDetails = event.response.claimsOverrideDetails || {};
event.response.claimsOverrideDetails.claimsToAddOrOverride = event.response.claimsOverrideDetails.claimsToAddOrOverride || {};

// Example of adding roles dynamically from DynamoDB to the JWT claims
event.response.claimsOverrideDetails.claimsToAddOrOverride['custom:user_roles'] = JSON.stringify(roles); // Add user roles
} catch(e) {
console.log("error updating id token claims", e)
}
console.log("JWT claims after modification:", JSON.stringify(event));
return event;
};

export { handler };
76 changes: 76 additions & 0 deletions services/admin/handlers/batchUpdateCognitoUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import AWS from "aws-sdk";
const cognito = new AWS.CognitoIdentityServiceProvider();
import { getUser } from "../../app-api/getUser";

async function updateUserAttribute(userPoolId, username, roles) {
const params = {
UserPoolId: userPoolId,
Username: username,
UserAttributes: [
{
Name: 'custom:user_roles',
Value: JSON.stringify(roles)
}
]
};

await cognito.adminUpdateUserAttributes(params).promise();
}

async function processCognitoUsers() {
const userPoolId = process.env.USER_POOL_ID;
console.log("user pool id: ", userPoolId)
let paginationToken = null;
let counter = 0;
let hasRolesCounter = 0;
let noRolesCounter =0;
do {
const params = {
UserPoolId: userPoolId,
AttributesToGet: ['email'],
PaginationToken: paginationToken
};

const listUsersResponse = await cognito.listUsers(params).promise();
console.log(listUsersResponse.Users.length + " users found")

for (const user of listUsersResponse.Users) {
const emailAttribute = user.Attributes.find(attr => attr.Name === 'email');
if (emailAttribute) {
const userEmail = emailAttribute.Value;

try {
const externalUser = await getUser(userEmail);
let roles = [""];
let roleList;
try{
roleList = externalUser.roleList;
}catch(error) {
noRolesCounter ++
console.log(userEmail + " has no roles");
}
if (roleList && roleList.length > 0 && roleList[0] != null) {
roles = externalUser.roleList.map(role => role.role);
hasRolesCounter ++;
} else {
console.log("user parsing error for user" + userEmail)
}
await updateUserAttribute(userPoolId, user.Username, roles);
} catch (error) {
console.error(`Error processing user ${userEmail}:`, error);
}
}
counter++;
}

paginationToken = listUsersResponse.PaginationToken;
} while (paginationToken);
console.log(counter+ "users modified, "+ hasRolesCounter + "users had roles and "+ noRolesCounter + " users had no roles")
}


export const main = async () => {
await processCognitoUsers().catch(console.error);
console.log("function complete")
}

10 changes: 10 additions & 0 deletions services/admin/handlers/insertNotification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"_comment": "Dates are passed in format YYYY-MM-DD or keywords 'today' or 'x days from now'",
"publicationDate": "2024-01-01",
"expiryDate": "7 days from now",
"header": "New Feature Release",
"body": "We have released a new feature. Check it out now!",
"buttonText": "Learn More",
"buttonLink": "https://example.com/feature",
"notificationType": "user"
}
150 changes: 150 additions & 0 deletions services/admin/handlers/insertNotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { DynamoDB } from "aws-sdk";
import { v4 as uuidv4 } from "uuid"; // Import UUID library
import { addDays } from "date-fns"; // Import a date manipulation library like date-fns for handling date parsing

const dynamoDb = new DynamoDB.DocumentClient(
process.env.IS_OFFLINE
? {
endpoint: "http://localhost:8000",
}
: {}
);
const oneMacTableName = process.env.oneMacTableName || "defaultTableName";

// Define types for event and notification record
interface EventInput {
publicationDate?: string;
expiryDate?: string;
header: string;
body: string;
buttonText?: string;
buttonLink?: string;
notificationType: "user" | "system"; // User-friendly notification type input
}

interface NotificationRecord {
pk: string;
sk: string;
GSI1pk: string;
GSI1sk: string;
publicationDate: string;
expiryDate: string;
header: string;
body: string;
buttonText?: string | null;
buttonLink?: string | null;
createdAt: string;
}

// Function to translate notificationType to GSI1pk value
function mapNotificationType(type: "user" | "system"): string {
return type === "user" ? "USER_NOTIFICATION" : "SYSTEM";
}

// Function to handle flexible date inputs
function parseDate(input?: string): string {
// If no input is provided, default to the current date
if (!input) {
return new Date().toISOString();
}

if (input === "today") {
return new Date().toISOString();
}

if (input.includes("days from now")) {
const days = parseInt(input.split(" ")[0], 10);
return addDays(new Date(), days).toISOString();
}

// Assuming user passes YYYY-MM-DD format for easier input
if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
return new Date(`${input}T00:00:00Z`).toISOString();
}

// Default to current date if input is invalid
return new Date().toISOString();
}

function validateEvent(event: EventInput): void {
const missingParams: string[] = [];

if (!event.header) {
missingParams.push("header");
}
if (!event.body) {
missingParams.push("body");
}
if (!event.notificationType) {
missingParams.push("notificationType");
}
if (missingParams.length > 0) {
throw new Error(`Missing event parameters: ${missingParams.join(", ")}`);
}

console.log("Event passed validation");
}

function generateNotificationId(): string {
return uuidv4();
}

function formatNotificationRecord(event: EventInput): NotificationRecord {
const notificationId = generateNotificationId(); // Generate a UUID for the notification ID
const pk = "SYSTEM"; // System-wide notification
const sk = `NOTIFICATION#${notificationId}`; // Unique notification identifier

// Default values for publicationDate and expiryDate with more user-friendly inputs
const publicationDate = parseDate(event.publicationDate); // Default to current date
const expiryDate = event.expiryDate
? parseDate(event.expiryDate)
: "9999-12-31T23:59:59Z"; // Default to far future date

// Use the translated notificationType
const GSI1pk = mapNotificationType(event.notificationType);
const GSI1sk = `${publicationDate}#${expiryDate}`; // Sort key for GSI based on dates
const createdAt = new Date().toISOString(); // Current timestamp

return {
pk,
sk,
GSI1pk,
GSI1sk,
publicationDate,
expiryDate,
header: event.header,
body: event.body,
buttonText: event.buttonText || null, // Optional field
buttonLink: event.buttonLink || null, // Optional field
createdAt,
};
}

async function insertNotification(record: NotificationRecord): Promise<void> {
const params: DynamoDB.DocumentClient.PutItemInput = {
TableName: oneMacTableName,
Item: record,
};

console.log("Inserting notification", params);

await dynamoDb.put(params).promise();
}

export const main = async (event: EventInput) => {
console.log("insertNotification.main", event);

validateEvent(event);

const notificationRecord = formatNotificationRecord(event);

await insertNotification(notificationRecord);

return {
statusCode: 200,
body: JSON.stringify({
message: "Notification inserted successfully",
record: notificationRecord, // Return the full inserted record
}),
};
};
27 changes: 26 additions & 1 deletion services/admin/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ frameworkVersion: "3"

useDotenv: true

variablesResolutionMode: 20210326

package:
individually: true

plugins:
- serverless-esbuild
- serverless-dotenv-plugin
- serverless-s3-bucket-helper

custom:
stage: ${opt:stage, self:provider.stage}
iamPermissionsBoundaryPolicy: ${ssm:/configuration/${self:custom.stage}/iam/permissionsBoundaryPolicy, ssm:/configuration/default/iam/permissionsBoundaryPolicy, ""}
oneMacTableName: onemac-${self:custom.stage}-one
userPoolName: ${self:custom.stage}-user-pool
userPoolId: ${cf:ui-auth-${self:custom.stage}.UserPoolId}

provider:
name: aws
runtime: nodejs20.x
Expand All @@ -37,14 +43,29 @@ provider:
- arn:aws:dynamodb:*:*:table/onemac-develop-one
- arn:aws:dynamodb:*:*:table/${self:custom.oneMacTableName}
- arn:aws:dynamodb:*:*:table/${self:custom.oneMacTableName}/index/*
- Effect: Allow
Action:
- cognito-idp:ListUsers
- cognito-idp:AdminUpdateUserAttributes
Resource: arn:aws:cognito-idp:${self:provider.region}:*:userpool/${self:custom.userPoolId}

environment:
NODE_OPTIONS: '--enable-source-maps'
oneMacTableName: ${self:custom.oneMacTableName}
USER_POOL_ID: ${self:custom.userPoolId}
layers:
- ${cf:aws-sdk-v2-layer-${self:custom.stage}.AwsSdkV2LambdaLayerQualifiedArn}


functions:

batchUpdateCognitoUsers:
handler: ./handlers/batchUpdateCognitoUsers.main
timeout: 360

addRolesToJWT:
handler: ./handlers/addRolesToJWT.handler
timeout: 360

resetData:
handler: ./handlers/resetData.main
timeout: 360
Expand Down Expand Up @@ -79,3 +100,7 @@ functions:
- schedule:
rate: rate(6 hours)
timeout: 180

insertNotification:
handler: ./handlers/insertNotification.main
timeout: 180
2 changes: 1 addition & 1 deletion services/app-api/getDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const getDetails = async (event) => {
if (!userRoleObj.isCMSUser && result.Item.reviewTeam)
delete result.Item.reviewTeam;

result.Item.actions = getActionsForPackage(
result.Item.actions = await getActionsForPackage(
result.Item.componentType,
originalStatus,
!!result.Item.latestRaiResponseTimestamp,
Expand Down
Loading

0 comments on commit 98474e1

Please sign in to comment.