Skip to content

Commit

Permalink
Revert "Remove ReadySetCyber (RSC) from TypeScript Backend. (#723)"
Browse files Browse the repository at this point in the history
This reverts commit fac878c.
  • Loading branch information
aloftus23 committed Nov 18, 2024
1 parent 7c5c248 commit 7dd81db
Show file tree
Hide file tree
Showing 15 changed files with 652 additions and 2 deletions.
11 changes: 9 additions & 2 deletions backend/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as savedSearches from './saved-searches';
import rateLimit from 'express-rate-limit';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { Organization, User, UserType, connectToDatabase } from '../models';
import * as assessments from './assessments';
import * as jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import fetch from 'node-fetch';
Expand Down Expand Up @@ -123,7 +124,8 @@ app.use(
cors({
origin: [
'http://localhost',
/^https:\/\/(.*\.)?crossfeed\.cyber\.dhs\.gov$/
/^https:\/\/(.*\.)?crossfeed\.cyber\.dhs\.gov$/,
/^https:\/\/(.*\.)?readysetcyber\.cyber\.dhs\.gov$/
],
methods: 'GET,POST,PUT,DELETE,OPTIONS'
})
Expand All @@ -139,7 +141,7 @@ app.use(
`${process.env.COGNITO_URL}`,
`${process.env.BACKEND_DOMAIN}`
],
frameSrc: ["'self'"],
frameSrc: ["'self'", 'https://www.dhs.gov/ntas/'],
imgSrc: [
"'self'",
'data:',
Expand Down Expand Up @@ -338,6 +340,7 @@ app.get('/', handlerToExpress(healthcheck));
app.post('/auth/login', handlerToExpress(auth.login));
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));
app.get(
Expand Down Expand Up @@ -830,6 +833,10 @@ authenticatedRoute.put(
'/notifications/:notificationId',
handlerToExpress(notifications.update)
);
//Authenticated ReadySetCyber Routes
authenticatedRoute.get('/assessments', handlerToExpress(assessments.list));

authenticatedRoute.get('/assessments/:id', handlerToExpress(assessments.get));

//************* */
// V2 Routes //
Expand Down
118 changes: 118 additions & 0 deletions backend/src/api/assessments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { validateBody, wrapHandler, NotFound, Unauthorized } from './helpers';
import { Assessment, connectToDatabase } from '../models';
import { isUUID } from 'class-validator';

/**
* @swagger
*
* /assessments:
* post:
* description: Save an RSC assessment to the XFD database.
* tags:
* - Assessments
*/
export const createAssessment = wrapHandler(async (event) => {
const body = await validateBody(Assessment, event.body);

await connectToDatabase();

const assessment = Assessment.create(body);
await Assessment.save(assessment);

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

/**
* @swagger
*
* /assessments:
* get:
* description: Lists all assessments for the logged-in user.
* tags:
* - Assessments
*/
export const list = wrapHandler(async (event) => {
const userId = event.requestContext.authorizer!.id;

if (!userId) {
return Unauthorized;
}

await connectToDatabase();

const assessments = await Assessment.find({
where: { user: userId }
});

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

/**
* @swagger
*
* /assessments/{id}:
* get:
* description: Return user responses and questions organized by category for a specific assessment.
* parameters:
* - in: path
* name: id
* description: Assessment id
* tags:
* - Assessments
*/
export const get = wrapHandler(async (event) => {
const assessmentId = event.pathParameters?.id;

if (!assessmentId || !isUUID(assessmentId)) {
return NotFound;
}

await connectToDatabase();

const assessment = await Assessment.findOne(assessmentId, {
relations: [
'responses',
'responses.question',
'responses.question.category',
'responses.question.resources'
]
});

if (!assessment) {
return NotFound;
}

// Sort responses by question.number and then by category.number
assessment.responses.sort((a, b) => {
const questionNumberComparison = a.question.number.localeCompare(
b.question.number
);
if (questionNumberComparison !== 0) {
return questionNumberComparison;
} else {
return a.question.category.number.localeCompare(
b.question.category.number
);
}
});

const responsesByCategory = assessment.responses.reduce((acc, response) => {
const categoryName = response.question.category.name;
if (!acc[categoryName]) {
acc[categoryName] = [];
}
acc[categoryName].push(response);
return acc;
}, {});

return {
statusCode: 200,
body: JSON.stringify(responsesByCategory)
};
});
7 changes: 7 additions & 0 deletions backend/src/api/scans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ export const SCAN_SCHEMA: ScanSchema = {
description:
'Creates domains from root domains by doing a single DNS lookup for each root domain.'
},
rscSync: {
type: 'fargate',
isPassive: true,
global: true,
description:
'Retrieves and saves assessments from ReadySetCyber mission instance.'
},
savedSearch: {
type: 'fargate',
isPassive: true,
Expand Down
76 changes: 76 additions & 0 deletions backend/src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
isGlobalWriteAdmin,
matchesUserRegion
} from './auth';
import { fetchAssessmentsByUser } from '../tasks/rscSync';

class UserSearch {
@IsInt()
Expand Down Expand Up @@ -269,6 +270,30 @@ If you encounter any difficulties, please feel free to reply to this email (or s
);
};

const sendRSCInviteEmail = async (email: string) => {
const staging = process.env.NODE_ENV !== 'production';

await sendEmail(
email,
'ReadySetCyber Dashboard Invitation',
`Hi there,
You've been invited to join ReadySetCyber Dashboard. To accept the invitation and start using your Dashboard, sign on at ${process.env.FRONTEND_DOMAIN}/readysetcyber/create-account.
CyHy Dashboard access instructions:
1. Visit ${process.env.FRONTEND_DOMAIN}/readysetcyber/create-account.
2. Select "Create Account."
3. Enter your email address and a new password for CyHy Dashboard.
4. A confirmation code will be sent to your email. Enter this code when you receive it.
5. You will be prompted to enable MFA. Scan the QR code with an authenticator app on your phone, such as Microsoft Authenticator. Enter the MFA code you see after scanning.
6. After configuring your account, you will be redirected to CyHy Dashboard.
For more information on using CyHy Dashboard, view the CyHy Dashboard user guide at https://docs.crossfeed.cyber.dhs.gov/user-guide/quickstart/.
If you encounter any difficulties, please feel free to reply to this email (or send an email to ${process.env.CROSSFEED_SUPPORT_EMAIL_REPLYTO}).`
);
};
/**
* @swagger
*
Expand Down Expand Up @@ -919,3 +944,54 @@ export const updateV2 = wrapHandler(async (event) => {
}
return NotFound;
});

/**
* @swagger
*
* /readysetcyber/register:
* post:
* description: New ReadySetCyber user registration.
* tags:
* - RSCUsers
*/
export const RSCRegister = wrapHandler(async (event) => {
const body = await validateBody(NewUser, event.body);
const newRSCUser = {
firstName: body.firstName,
lastName: body.lastName,
email: body.email.toLowerCase(),
userType: UserType.READY_SET_CYBER
};

await connectToDatabase();

// Check if user already exists
let user = await User.findOne({
email: newRSCUser.email
});
if (user) {
console.log('User already exists.');
return {
statusCode: 422,
body: 'User email already exists. Registration failed.'
};
// Create if user does not exist
} else {
user = User.create(newRSCUser);
await User.save(user);
// Fetch RSC assessments for user
await fetchAssessmentsByUser(user.email);
// Send email notification
if (process.env.IS_LOCAL!) {
console.log('Cannot send invite email while running on local.');
} else {
await sendRSCInviteEmail(user.email);
}
}

const savedUser = await User.findOne(user.id);
return {
statusCode: 200,
body: JSON.stringify(savedUser)
};
});
34 changes: 34 additions & 0 deletions backend/src/models/assessment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
BaseEntity,
Column,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn
} from 'typeorm';
import { Response } from './response';
import { User } from './user';

@Entity()
export class Assessment extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
createdAt: Date;

@Column()
updatedAt: Date;

@Column({ unique: true })
rscId: string;

@Column()
type: string;

@ManyToOne(() => User, (user) => user.assessments)
user: User;

@OneToMany(() => Response, (response) => response.assessment)
responses: Response[];
}
26 changes: 26 additions & 0 deletions backend/src/models/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
BaseEntity,
Column,
Entity,
OneToMany,
PrimaryGeneratedColumn
} from 'typeorm';
import { Question } from './question';

@Entity()
export class Category extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
name: string;

@Column({ unique: true })
number: string;

@Column({ nullable: true })
shortName: string;

@OneToMany(() => Question, (question) => question.category)
questions: Question[];
}
10 changes: 10 additions & 0 deletions backend/src/models/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import {
// Models for the Crossfeed database
ApiKey,
Notification,
Assessment,
Category,
Cpe,
Cve,
Domain,
Organization,
OrganizationTag,
Question,
Resource,
Response,
Role,
SavedSearch,
Scan,
Expand Down Expand Up @@ -179,11 +184,16 @@ const connectDb = async (logging?: boolean) => {
database: process.env.DB_NAME,
entities: [
ApiKey,
Assessment,
Category,
Cpe,
Cve,
Domain,
Organization,
OrganizationTag,
Question,
Resource,
Response,
Role,
SavedSearch,
OrganizationTag,
Expand Down
5 changes: 5 additions & 0 deletions backend/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './api-key';
export * from './assessment';
export * from './category';
export * from './connection';
export * from './cpe';
export * from './cve';
Expand All @@ -7,6 +9,9 @@ export * from './organization';
export * from './organization-tag';
export * from './material-views';
export * from './notification';
export * from './question';
export * from './resource';
export * from './response';
export * from './role';
export * from './saved-search';
export * from './scan';
Expand Down
Loading

0 comments on commit 7dd81db

Please sign in to comment.