Skip to content
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

100 UI admin site maintenance notifications #148

Merged
merged 38 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7d9dcc7
Update routing for /scans from ScanForm.tsx
ameliav Apr 4, 2024
b7fbd03
Remove comments and alphabetize imports in App.tsx
ameliav Apr 4, 2024
7359da7
Update exports in scans/index.ts
ameliav Apr 4, 2024
4bc60d9
Update and alphabetize exports in pages/index.ts
ameliav Apr 4, 2024
b1da8a8
Comment edit button in Scans with todo to refactor if needed
ameliav Apr 4, 2024
9f907bb
Add frontend Notifications folder and files
ameliav Apr 4, 2024
7fa87cb
Update AdminTools to not use routing when switching tabs
ameliav Apr 4, 2024
7a7bbc9
Create Notification model
DJensen94 Apr 5, 2024
e4016a3
Add notification to db_connection
DJensen94 Apr 5, 2024
b1b7e6b
Add api endpoints
DJensen94 Apr 5, 2024
960a22f
Remove pagination to notifications table
ameliav Apr 8, 2024
88a04dd
Merge branch 'develop' into 100-ui-admin-site-maintenance-notifications
ameliav Apr 9, 2024
1204ae0
update the app to read endpoints
DJensen94 Apr 9, 2024
6c3c5ad
update api request type for create
DJensen94 Apr 9, 2024
26667be
Remove auth permission to view notification
DJensen94 Apr 9, 2024
609c3c9
Change location of notifications app api
DJensen94 Apr 10, 2024
0316296
remove typo
DJensen94 Apr 10, 2024
0ab9ff2
Add new /types/notification.ts file
ameliav Apr 10, 2024
4bf5cba
Add minor maintenance message conditional to AuthLogin.tsx
ameliav Apr 10, 2024
b920ca0
Update values in types/notification.js
ameliav Apr 12, 2024
86f18f8
Update notifications page table and form data and display
ameliav Apr 12, 2024
4d89d2f
Update result form values and unused jsx in Notifications.tsx
ameliav Apr 12, 2024
9379d7a
Add form validation to Notifications.tsx
ameliav Apr 15, 2024
c30ae7d
Update display with alert when no table data in Notifications.tsx
ameliav Apr 16, 2024
f030621
Update header drawer button to not be shown at login screen
ameliav Apr 18, 2024
860935c
Update InfoDialog.tsx to remove unused import
ameliav Apr 18, 2024
ff88548
Add frontend/src/utils/dateUtils for date utility functions
ameliav Apr 19, 2024
9f95c3a
Update types/notification.ts
ameliav Apr 19, 2024
5ad2b61
Add post and put logic for Notifications.tsx
ameliav Apr 19, 2024
31492ce
Update the id and format in the backend delete notification api
ameliav Apr 23, 2024
da9603a
Add delete api call functionality to frontend notification.tsx
ameliav Apr 23, 2024
0cab977
Update /admin-tools routing in frontend
ameliav Apr 23, 2024
0486a40
Simplify notification logic on front page in AuthLogin.tsx
ameliav Apr 23, 2024
6e979e3
Update display messages in the admin-tools notification tab
ameliav Apr 23, 2024
5e0fadd
Add backend/frontend logic to blocking user logins for major maintena…
nickviola May 10, 2024
8f4c535
Resolve merge conflicts
nickviola May 10, 2024
c3af8b7
Removed duplicate imports in backend/src/models/connection.ts
JCantu248 May 14, 2024
cfb4c67
Fix linter errors in backend/src/models/notifications.ts
JCantu248 May 14, 2024
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
17 changes: 17 additions & 0 deletions backend/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as auth from './auth';
import * as cpes from './cpes';
import * as cves from './cves';
import * as domains from './domains';
import * as notifications from './notifications';
import * as search from './search';
import * as vulnerabilities from './vulnerabilities';
import * as organizations from './organizations';
Expand Down Expand Up @@ -117,6 +118,8 @@ app.post('/auth/callback', handlerToExpress(auth.callback));
app.post('/users/register', handlerToExpress(users.register));
app.post('/readysetcyber/register', handlerToExpress(users.RSCRegister));

app.get('/notifications', handlerToExpress(notifications.list));

const checkUserLoggedIn = async (req, res, next) => {
req.requestContext = {
authorizer: await auth.authorize({
Expand Down Expand Up @@ -453,6 +456,20 @@ authenticatedRoute.put(
handlerToExpress(users.registrationDenial)
);

authenticatedRoute.delete(
'/notifications/:notificationId',
handlerToExpress(notifications.del)
);

authenticatedRoute.post(
'/notifications',
handlerToExpress(notifications.create)
);

authenticatedRoute.put(
'/notifications/:notificationId',
handlerToExpress(notifications.update)
);
//Authenticated ReadySetCyber Routes
authenticatedRoute.get('/assessments', handlerToExpress(assessments.list));

Expand Down
13 changes: 13 additions & 0 deletions backend/src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as jwt from 'jsonwebtoken';
import { APIGatewayProxyEvent } from 'aws-lambda';
import * as jwksClient from 'jwks-rsa';
import { createHash } from 'crypto';
import { shouldBlockLogin } from './helpers';

export interface UserToken {
email: string;
Expand All @@ -22,6 +23,7 @@ export interface UserToken {
dateAcceptedTerms: Date | undefined;
acceptedTermsVersion: string | undefined;
lastLoggedIn: Date | undefined;
loginBlockedByMaintenance: boolean | false;
}

interface CognitoUserToken {
Expand Down Expand Up @@ -83,6 +85,7 @@ export const userTokenBody = (user): UserToken => ({
dateAcceptedTerms: user.dateAcceptedTerms,
acceptedTermsVersion: user.acceptedTermsVersion,
lastLoggedIn: user.lastLoggedIn,
loginBlockedByMaintenance: user.loginBlockedByMaintenance,
roles: user.roles
.filter((role) => role.approved)
.map((role) => ({
Expand Down Expand Up @@ -143,6 +146,16 @@ export const callback = async (event, context) => {

const idKey = `${process.env.USE_COGNITO ? 'cognitoId' : 'loginGovId'}`;

// Check shouldBlockLogin due to maintenance
if (user) {
console.log('Checking if login should be blocked due to Maintenance...');
const loginBlocked = await shouldBlockLogin(user);
console.log('Login blocked response from callback: ', loginBlocked);
user.loginBlockedByMaintenance = loginBlocked;
user.save();
}
console.log(user);

// If user does not exist, create it
if (!user) {
user = User.create({
Expand Down
62 changes: 62 additions & 0 deletions backend/src/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
} from 'aws-lambda';
import { ValidationOptions, validateOrReject } from 'class-validator';
import { ClassType } from 'class-transformer/ClassTransformer';
import { LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { plainToClass } from 'class-transformer';
import S3Client from '../tasks/s3-client';
import { SES } from 'aws-sdk';
import * as nodemailer from 'nodemailer';
import { Notification, User, connectToDatabase } from '../models';
import * as handlebars from 'handlebars';

export const REGION_STATE_MAP = {
Expand Down Expand Up @@ -303,3 +305,63 @@ export const sendRegistrationApprovedEmail = async (
console.log('Email error: ', errorMessage);
}
};

/**
* Function to find notifications that are currently active based on the current datetime.
* @returns A promise containing an array of active `Notification` entities.
*/
async function isMajorActiveMaintenance(): Promise<boolean> {
const now = new Date();
console.log(now);
try {
// DB connection
await connectToDatabase();

// Query major/active notifications with startDatetime <= now and endDatetime >= now
const notifications = await Notification.find({
where: [
{
startDatetime: LessThanOrEqual(now),
endDatetime: MoreThanOrEqual(now),
status: 'active',
maintenanceType: 'major'
}
],
order: {
startDatetime: 'DESC',
id: 'DESC'
}
});

// Log notifications
console.log('Current notifications check Result: ', notifications);

// Return True if Notifcations exist
// if (notifications.length > 0) {
if (notifications) {
console.log('isMajorActiveMaintenance is returning True.');
return true;
} else {
console.log('isMajorActiveMaintenance is returning False.');
return false;
}
} catch (err) {
console.error('Error:', err);
// Catch-All return false on error
return false;
}
}

export async function shouldBlockLogin(user: User): Promise<boolean> {
// Check if there's active maintenance
const activeMaintenance = await isMajorActiveMaintenance();

// Block login for non-globalAdmin users during active maintenance
if (activeMaintenance && user.userType !== 'globalAdmin') {
console.log('Login should be blocked.');
return true; // Return true to indicate that login should be blocked
} else {
console.log('Login should not be blocked.');
return false; // Return false to indicate that login should not be blocked
}
}
158 changes: 158 additions & 0 deletions backend/src/api/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { IsString, isUUID, IsOptional, IsDateString } from 'class-validator';
import { Notification, connectToDatabase } from '../models';
import { validateBody, wrapHandler, NotFound, Unauthorized } from './helpers';
import { isGlobalWriteAdmin } from './auth';

class NewNotification {
@IsDateString()
startDatetime?: string;

@IsDateString()
endDatetime?: string;

@IsString()
maintenanceType?: string;

@IsString()
@IsOptional()
status: string;

@IsString()
@IsOptional()
message: string;

@IsString()
@IsOptional()
updatedBy: string;
}
/**
* @swagger
*
* /notifications:
* post:
* description: Create a new notification.
* tags:
* - Notifications
*/
export const create = wrapHandler(async (event) => {
if (!isGlobalWriteAdmin(event)) return Unauthorized;
const body = await validateBody(NewNotification, event.body);
await connectToDatabase();

const notification = await Notification.create({
...body
});
const res = await notification.save();
return {
statusCode: 200,
body: JSON.stringify(res)
};
});

/**
* @swagger
*
* /notifications/{id}:
* delete:
* description: Delete a particular notification.
* parameters:
* - in: path
* name: id
* description: Notification id
* tags:
* - Notifications
*/
export const del = wrapHandler(async (event) => {
if (!isGlobalWriteAdmin(event)) return Unauthorized;
await connectToDatabase();
const id = event.pathParameters?.notificationId;
if (!id || !isUUID(id)) {
return NotFound;
}
const result = await Notification.delete(id);
return {
statusCode: 200,
body: JSON.stringify(result)
};
});

/**
* @swagger
*
* /notifications:
* get:
* description: List all notifications.
* tags:
* - Notifications
*/
export const list = wrapHandler(async (event) => {
console.log('list function called with event: ', event);

// if (!isGlobalWriteAdmin(event)) {
// return {
// //TODO: Should we return a 403?
// statusCode: 200,
// body: JSON.stringify([])
// };
// }
await connectToDatabase();
console.log('Database connected');

const result = await Notification.find({
order: {
startDatetime: 'DESC',
id: 'DESC'
}
});

console.log('Notification.find result: ', result);

return {
statusCode: 200,
body: JSON.stringify(result)
};
});

/**
* @swagger
*
* /notifications/{id}:
* put:
* description: Update a particular notification.
* parameters:
* - in: path
* name: id
* description: notification id
* tags:
* - Notifications
*/

export const update = wrapHandler(async (event) => {
if (!isGlobalWriteAdmin(event)) return Unauthorized;
// Get the notification id from the path
const id = event.pathParameters?.notificationId;

// confirm that the id is a valid UUID
if (!id || !isUUID(id)) {
return NotFound;
}

// Validate the body
const validatedBody = await validateBody(NewNotification, event.body);

// Connect to the database
await connectToDatabase();

// Update the organization
const updateResp = await Notification.update(id, validatedBody);

// Handle response
if (updateResp) {
const updatedNot = await Notification.findOne(id);
return {
statusCode: 200,
body: JSON.stringify(updatedNot)
};
}
return NotFound;
});
4 changes: 4 additions & 0 deletions backend/src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class UpdateUser {
@IsString()
@IsOptional()
role: string;

@IsBoolean()
@IsOptional()
loginBlockedByMaintenance: boolean;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions backend/src/models/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { createConnection, Connection } from 'typeorm';
import {
// Models for the Crossfeed database
ApiKey,
SavedSearch,
JCantu248 marked this conversation as resolved.
Show resolved Hide resolved
Notification,
OrganizationTag,
JCantu248 marked this conversation as resolved.
Show resolved Hide resolved
Assessment,
Category,
Cpe,
Expand Down Expand Up @@ -135,6 +138,8 @@ const connectDb = async (logging?: boolean) => {
Response,
Role,
SavedSearch,
OrganizationTag,
Notification,
Scan,
ScanTask,
Service,
Expand Down
1 change: 1 addition & 0 deletions backend/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './cve';
export * from './domain';
export * from './organization';
export * from './organization-tag';
export * from './notification';
export * from './question';
export * from './resource';
export * from './response';
Expand Down
Loading
Loading