first commit of Project Files

main
Alex 5 months ago
parent a2ca9bb7a4
commit c5adde5509

49
CTF/.gitignore vendored

@ -0,0 +1,49 @@
# Ignore node_modules in the Website folder
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
CTF-Frontend/node_modules
CTF-Server/node_modules
Website/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
CTF-Frontend/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
#environment files
.env
#Vite

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

@ -0,0 +1,59 @@
# CTFFrontend
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.3.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

@ -0,0 +1,104 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"CTF-Frontend": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/ctf-frontend",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css",
"node_modules/@xterm/xterm/css/xterm.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"options":{
"proxyConfig": "./proxy.conf.json"
},
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "CTF-Frontend:build:production"
},
"development": {
"buildTarget": "CTF-Frontend:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css",
"node_modules/@xterm/xterm/css/xterm.css"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,41 @@
{
"name": "ctf-frontend",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"type": "module",
"dependencies": {
"@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0",
"@angular/forms": "^19.2.0",
"@angular/platform-browser": "^19.2.0",
"@angular/platform-browser-dynamic": "^19.2.0",
"@angular/router": "^19.2.0",
"@xterm/addon-attach": "^0.11.0",
"rxjs": "~7.8.0",
"socket.io-client": "^4.8.1",
"tslib": "^2.3.0",
"xterm": "^5.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.2.3",
"@angular/cli": "^19.2.3",
"@angular/compiler-cli": "^19.2.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.7.2"
}
}

@ -0,0 +1,24 @@
{
"/api": {
"target": "http://localhost:3000",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/api": ""
},
"logLevel": "debug"
},
"/api/socket.io": {
"target": "http://localhost:300",
"secure": false,
"ws": true,
"changeOrigin": true,
"logLevel": "debug"
},
"/ws": {
"target": "ws://localhost:3000",
"ws": true,
"secure": false,
"changeOrigin": true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,312 @@
import { Router } from '@angular/router';
import { Contest } from '../models/contest.model';
import { Flag } from '../models/flag.model';
import { Renderer2, ElementRef } from '@angular/core';
import { getEmail, updateContainer, gotoPage } from './Helpers';
import { TerminalService } from './terminal.service';
import { SocketIOService } from '../notifications/socket-io.service';
import { Socket } from 'socket.io-client';
export async function loadPage(renderer: Renderer2, el: ElementRef, ts: any): Promise<void> {
const isPractice = window.location.href.includes("Prac");
const taskbar: HTMLElement = document.getElementById('Taskbar')!;
// basics for use in multiple methods
const elements = {
flagName: el.nativeElement.querySelector('#Flag_Name') as HTMLElement,
desc: el.nativeElement.querySelector('#Desc') as HTMLElement,
contName: el.nativeElement.querySelector('#ContHeader') as HTMLElement,
contestName: el.nativeElement.querySelector('#ContName') as HTMLElement,
contDesc: el.nativeElement.querySelector('#ContDesc') as HTMLElement,
hintDesc: el.nativeElement.querySelector('#Hint_Desc') as HTMLElement,
HintButts: el.nativeElement.querySelector('#Hint_Butts') as HTMLElement
}
await LoadContest(el, elements, ts);
}
// get the params to see if admin or not
export function getAdmin(): string | null {
const url: string = window.location.search;
const params = new URLSearchParams(url);
return params.get('admin');
}
// load the current active contest
async function LoadContest(el: ElementRef, elements: any, ts: any) {
const contestID = new URLSearchParams(window.location.search).get('contestID');
console.log("Contest ID:", contestID);
let contest: Contest | undefined;
if(contestID){
const data = { contestID: contestID};
const res = await fetch('api/contests/getContestByID', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if(res.ok) {
contest = await res.json();
console.log("contest", contest);
}
}
else {
const data = { email: getEmail() };
const res = await fetch('api/contests/getActiveContest', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
contest = await res.json();
}
}
const msg = el.nativeElement.querySelector('#DefaultMessage') as HTMLElement;
if (!contest) {
alert('No Contest is Active');
msg.classList.remove('hidden');
}
else {
msg.classList.add('hidden');
console.log('Contest:', contest);
elements.contName.innerHTML = contest.Name + ' Contest';
await LoadElements(contest, el, elements, ts);
}
}
// load all the dynamic elements
async function LoadElements(contest: Contest, el: ElementRef, elements: any, ts: any) {
const data = { contest: contest.ContestID };
const res = await fetch('api/flags/getAllFlagsFromContest', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if(!res.ok){
console.error('Error fetching flags for contest');
return;
}
const flagdata: Flag[] = await res.json();
console.log("Fetched Flags:", flagdata);
const UL = el.nativeElement.querySelector('#FlagList') as HTMLElement;
if(UL)
UL.innerHTML = '';
if(flagdata.length === 0){
console.warn("no flags found for contest");
return;
}
if(elements.contName) elements.contName.style.display = 'none';
if(elements.contDesc) elements.contDesc.style.display = 'none';
const tmp = await getActiveFlag(getEmail(), contest.ContestID, el, elements);
if(tmp.ActiveFlag === 'ubuntu'){
elements.contName.innerHTML = contest.Name;
elements.contDesc.innerHTML = contest.Description;
elements.contName.style.display = 'block';
elements.contDesc.style.display = 'block';
}
const submitFlag = el.nativeElement.querySelector('#Submit_Flag_Stuff') as HTMLElement;
const hintStuff = el.nativeElement.querySelector('#Hint_Stuff') as HTMLElement;
// add the contest name to the list
elements.flagName.textContent = '';
elements.desc.textContent = '';
if(elements.hintButts) elements.hintButts.innerHTML = '';
if(submitFlag) submitFlag.classList.add('hidden');
hintStuff.classList.add('hidden');
flagdata.forEach(flag => {
console.log(flag);
const li = document.createElement('li');
const a = document.createElement('a');
a.textContent = flag.Name;
li.onclick = () => setNewFlag(flag, el, elements, ts);
// add to list
li.appendChild(a);
UL.appendChild(li);
});
}
// get the active flag of a user
async function getActiveFlag(email: string, contest: number, el: ElementRef, elements: any) {
const data = { email: email };
const res = await fetch('api/flags/getActiveFlag', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) { // get the active flag onto html
const ret = await res.json();
// go through all the flags gotten and make sure it's the right one for this contest
for (let i=0; i < ret.length; i++) {
if (contest === ret[i].ContestID) setUpFlag(ret[i], el, elements);
}
}
return { ActiveFlag: 'ubuntu' };
}
let currentFlagID: number | null = null;
// get the new flag the user clicked on
export async function setNewFlag(flag: Flag, el: ElementRef, elements: any, ts: any) {
currentFlagID = flag.FlagID;
console.log("The new flagID is", currentFlagID);
const submitFlagButt = el.nativeElement.querySelector('#Submit_Flag_Stuff') as HTMLElement;
const hintstuff = el.nativeElement.querySelector('#Hint_Stuff') as HTMLElement;
const resultelement = el.nativeElement.querySelector('#Submission_Result') as HTMLElement;
const input = el.nativeElement.querySelector('#Submit_Flag_Stuff input[type="text"]') as HTMLInputElement;
if(submitFlagButt) {
submitFlagButt.classList.remove('hidden');
// clear the result and the input box
if(resultelement){
resultelement.textContent = '';
resultelement.classList.remove('Incorrect', 'Correct');
}
if(input){
input.value = '';
input.placeholder = 'NMUCTF${FLAG}';
}
}
if(hintstuff) hintstuff.classList.remove('hidden');
const email = getEmail();
const data = { FlagImage: flag.Image, email: email };
const res = await fetch('api/setNewActiveFlag', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) { // get the flag details onto html
const ret = await res.json();
setUpFlag(flag, el, elements);
await updateContainer(email);
console.log('Setting new Flag');
ts.ClearAndSetTerminal(); // clears the terminal and replaces with the new one for the specific flag
}
else
console.error('Failed to set new flag.');
}
// set the HTML to the correct flag
function setUpFlag(flag: Flag, el: ElementRef, elements: any) {
// set up the basics
elements.flagName.innerHTML = flag.Name;
elements.desc.innerHTML = flag.Description;
if(elements.HintButts) elements.HintButts.innerHTML = '';
if(elements.hintDesc) elements.hintDesc.innerHTML = '';
// set up the hints buttons
if (flag.Hint1) {
const hint1 = document.createElement('button');
hint1.textContent = 'Hint 1';
hint1.addEventListener('click', function() {
elements.hintDesc.innerHTML = flag.Hint1;
});
elements.HintButts.appendChild(hint1);
}
if (flag.Hint2) {
const hint2 = document.createElement('button');
hint2.textContent = 'Hint 2';
hint2.addEventListener('click', function() {
elements.hintDesc.innerHTML = flag.Hint2;
});
elements.HintButts.appendChild(hint2);
}
if (flag.Hint3) {
const hint3 = document.createElement('button');
hint3.textContent = 'Hint 3';
hint3.addEventListener('click', function() {
elements.hintDesc.innerHTML = flag.Hint3;
});
elements.HintButts.appendChild(hint3);
}
// if theres at least one hint have a placeholder for user
if (flag.Hint1 || flag.Hint2 || flag.Hint3) {
elements.hintDesc.textContent = 'click to see a Hint';
}
else {
elements.hintDesc.textContent = 'there are no Hints at all good luck :('
}
}
/*async function stopContainer(){
if(container){
await container.kill();
await container.remove({force:true});
}
}*/
function getCurrentFlagID(){
return currentFlagID;
}
export function handleSubmission(el: ElementRef, socket: SocketIOService){
const url = window.location.href;
if(url.includes("Prac")){
checkSubmission(true, el, socket);
}
else{
checkSubmission(false, el, socket);
}
}
async function checkSubmission(isPractice: boolean, el: ElementRef, socket: SocketIOService){
const submittedFlag = el.nativeElement.querySelector('#Submit_Flag input[type="text"]').value.trim();
const email = getEmail();
const flagID = getCurrentFlagID();
console.log("FLAG ID IS", flagID);
const flagName = el.nativeElement.querySelector('#Flag_Name').textContent;
const resultelement = el.nativeElement.querySelector('#Submission_Result');
if(!submittedFlag){
resultelement.textContent = "Please enter a flag.";
return;
}
let res, result;
if(!isPractice){
res = await fetch('api/checkFlagSubmission', {
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({ email, flagID, submittedFlag })
});
}
else{
res = await fetch('api/checkPracSubmission', {
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({ email, flagID, submittedFlag })
});
}
result = await res.json();
if(result.correct){
resultelement.classList.add('Correct');
resultelement.classList.remove('Incorrect');
resultelement.textContent = 'Correct! :)';
console.log("SENDING NOTIF", email, flagName);
/*socket.emit('submission-notification', {
email, flagName, time: new Date().toISOString()
});*/
}
else{
resultelement.classList.add('Incorrect');
resultelement.classList.remove('Correct');
resultelement.textContent = 'Incorrect! :(';
}
}

@ -0,0 +1,207 @@
/*
Jordan Latimer
Alex Miller
Helper functions used by a variety of javascript files
*/
import { Router } from '@angular/router';
import { Contest } from '../models/contest.model';
import { Flag } from '../models/flag.model';
import { Terminal } from 'xterm';
import { AttachAddon } from '@xterm/addon-attach';
import { PopulateUserTables } from './User-pfp';
let term: Terminal | undefined;
// get the email of the client
export function getEmail(): string{
return sessionStorage.getItem('email') || '';
}
// got to a specific page
export function gotoPage(router: Router, page: string): void {
router.navigate([page]);
}
export function logOut(router: Router){
router.navigate(['']);
}
// popup window for a specific page with the email
export function Popup(site: string): void {
const width = 500;
const height = 500;
// get the right placement and size for any screen
const x = screenX + (window.screen.width / 2) - (width / 2);
const y = screenY + (window.screen.height / 2) - (height / 2);
// add the features and the email inside the URL and open the window
const features = `width=${width},height=${height},left=${x},top=${y}`;
window.open(site,'popup',features);
}
// get all the table contents and add them in the respective table
export async function PopulateTable(page: number, InsertIntoTable: (data: Contest[]) => void): Promise<void> {
const data = { email: getEmail() };
const res = await fetch('api/contests/getContests', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
const list = await res.json();
if (page === 0) InsertIntoTable(list);
else PopulateUserTables(list);
}
}
export async function getContests(): Promise<Contest[]> {
const data = { email: getEmail() };
const res = await fetch('api/contests/getContests', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
const list: Contest[] = await res.json();
return list;
}
else {
console.error('Failed to fetch contests');
return [];
}
}
const flagItems = document.querySelectorAll('#Flag_Sidebar li');
flagItems.forEach(item => {
item.addEventListener('click', function() {
flagItems.forEach(f => f.classList.remove('selected-flag'));
item.classList.add('selected-flag');
})
});
export async function InsertIntoTable(data: Contest[], router: Router): Promise<void> {
// Past Contest Table
const PCT = document.querySelector('#Past_Contests tbody') as HTMLTableSectionElement | null;
// Active Contest Table
const ACT = document.querySelector('#Current_Contest tbody') as HTMLTableSectionElement | null;
if(!PCT || !ACT)
return;
PCT.innerHTML = '';
ACT.innerHTML = '';
// list of flags
const res2 = await fetch('api/flags/getAllFlags');
const flaglist: Flag[] = await res2.json();
const email = getEmail();
const res3 = await fetch('api/contests/getActiveContest', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email})
})
let activeContest = await res3.json().catch(() => null);
if(activeContest && activeContest.error === 'NO CONTEST FOUND') activeContest = null;
data.forEach((contest: Contest) => {
const row = document.createElement('tr');
const name = document.createElement('td');
const flags = document.createElement('td');
const leader = document.createElement('td');
name.textContent = contest.Name;
// get the amount of flags in the table
let i = 0;
flaglist.forEach(flag => {
if (flag.ContestID === contest.ContestID) i++;
});
flags.textContent = i.toString();
// get the button for the leaderboard
const leaderbutton = document.createElement('button');
leaderbutton.textContent = 'view';
leaderbutton.addEventListener('click', function() {
// Popup('leaderboard.html' ,getEmail(), 'ContestName=' + contest.Name); Uncomment when we finish leaderboard implementation
});
leader.appendChild(leaderbutton);
row.appendChild(name);
row.appendChild(flags);
if (contest.IsActive === 1) {
const button = document.createElement('td');
const JoinButton = document.createElement('button');
JoinButton.textContent = 'Join';
JoinButton.addEventListener('click', function() {
router.navigate(['/contest-page']);
});
button.appendChild(JoinButton);
row.appendChild(button);
ACT.appendChild(row);
}
else {
row.appendChild(leader);
if(!activeContest){
const pracButtCell = document.createElement('td');
const pracButt = document.createElement('button');
pracButt.textContent = 'Practice';
pracButt.addEventListener('click', function() {
console.log("contestID", contest.ContestID);
router.navigate(['/contest-page'], { queryParams: { email: email, contestID: contest.ContestID, Prac: true } });
});
pracButtCell.appendChild(pracButt);
row.appendChild(pracButtCell);
}
PCT.appendChild(row);
}
});
if(!activeContest){
const pracRow = document.querySelector('#Past_Contests thead tr');
if(pracRow){
const pracRowCell = document.createElement('th');
pracRowCell.textContent = 'Practice';
pracRow.appendChild(pracRowCell);
}
}
}
// update the container
export async function updateContainer(email: string){
const res = await fetch(`api/updateContainer/${email}`, { method: 'POST'});
if(res.ok){
console.log('Container updated successfully');
}
else
console.log('Failed to update container');
}
// reattach the terminal web socket
export async function reattachTerminal(term: Terminal){
console.log('reattaching termnial');
if(term) {
term.dispose();
}
term = new Terminal();
const container = document.getElementById('Linux_Shell');
if(container && term) {
term.open(container);
}
const socket = new WebSocket('/ws');
socket.onopen = () => {
socket.send(JSON.stringify(getEmail()));
console.log('Websocket opened');
}
const attachAddon = new AttachAddon(socket);
term.loadAddon(attachAddon);
term.focus();
socket.onclose = () => {
console.log("socket closed");
}
}

@ -0,0 +1,181 @@
/*
Alex Miller
Jordan Latimer
Client code for Profile Page for Users
*/
import { Contest } from '../models/contest.model';
import { Flag } from '../models/flag.model';
import { User } from '../models/user.model';
import { getEmail, Popup, InsertIntoTable, getContests } from './Helpers';
// sets of names to pull randomly from for random names
let Adjectives = [
'Aspiring', 'Strict','Electronic','Talented','Gifted','Wonderful',
'Amazing','Inspiring','First','Second','Third','Cowardly','True',
'Coordinated','Sneaky','Concerned','Courageous','Nice','Mean','Sharp',
'Mysterious','Magical','Tame','Well-off','Overconfident','Cautious',
'Wandering','Wrathful','Important','Jolly','Mistaken','Admirable'
]
let Animals = [
'Fox','Turtle','Penguin','Robin','Shark','Giraffe','Kangaroo','Cat','Dog',
'Fish','Bear','Lion','Hippo','Duck','Octopus','Elephant','Bunny','Rabbit',
'Mouse','Pig','Cow','Chicken','Rooster','Ladybug','Firefly','Mosquito','Snail',
'Koala','Platypus','Woodchuck','Polarbear','Rat','Bull','Newt','Deer','Fawn',
'Cheetah','Leopard','Beaver','Llama','Guineapig','Squirrel','Eagle','Moose'
]
let Colors = [
'Green', 'Blue','Aqua','Red','Purple','Pink','Gray','Black','Brown','Teal',
'Limegreen','White','Yellow','Maroon','Violet', 'Coral','Emerald','Orange',
'Darkgreen','Navyblue','Cyan','Crimson','Bronze','Gold','Silver','Amber',
'Turquoise','Indigo','Fuchsia','Magenta','Platinum','Tan','Jade','Ruby'
]
let Things = [
'Chair','Desk','Lamp','Keys','Bottle','Wall','Window','Barn','House','Door',
'Outlet','TV','Screen','Road','Lightbulb','Sidewalk','Keyboard','Paper','Plastic',
'Remote','Phone','Computer','Can','Table','Backpack','Fan','Carpet','Pen','Pencil',
'Notebook','Clock','Tree','Pants','Shorts','Flannel','Jacket','Apple','Banana','Watch',
'Apricot','Socks','Pizza','Pasty','Car','Airplane','Bus','Scissors','Rock','Water'
]
let Name = '';
// on load, populate tables and set the users name inside the input field
export async function loadTable(): Promise<string> {
let contests = await getContests();
PopulateUserTables(contests);
const data = { email: getEmail() };
const res = await fetch('api/users/getUsername', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
})
if (res.ok) {
const ret = await res.json();
return ret;
}
return '';
}
// populate both user tables
export async function PopulateUserTables(PCData: Contest[]): Promise<void> {
const TB = document.querySelector('#Past_Contests tbody') as HTMLElement;
TB.innerHTML = "";
// get all of the flags
const res = await fetch('api/flags/getAllFlags');
const PCData2: Flag[] = await res.json();
let totalflagamount = 0;
// insert the amount of flags per contests
PCData.forEach(contest => {
let flagamount = 0;
const row = document.createElement('tr');
const name = document.createElement('td');
name.textContent = contest.Name;
const flags = document.createElement('td');
PCData2.forEach(flag => {
if (flag.ContestID === contest.ContestID) flagamount++;
});
flags.textContent = flagamount.toString();
totalflagamount += flagamount;
const leader = document.createElement('td');
// get the button for the leaderboard
const leaderbutton = document.createElement('button');
leaderbutton.textContent = 'view';
leaderbutton.setAttribute('id','ViewLeaderboard');
leaderbutton.addEventListener('click', function() {
//Popup('leaderboard.html' ,getEmail(), 'ContestName=' + contest.Name); Uncomment when leaderboard is implemented
});
leader.appendChild(leaderbutton);
// add to the table
row.appendChild(name);
row.appendChild(flags);
row.appendChild(leader);
TB.appendChild(row);
});
// get the user information
const data = { email: getEmail() };
const res2 = await fetch('api/users/getUser', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res2.ok) {
const Rdata = await res2.json();
const TB2 = document.querySelector('#Record tbody') as HTMLTableSectionElement;
if(TB2)
TB2.innerHTML = '';
// create the table for the users record, flags completed, and total flags
const row = document.createElement('tr');
const Total = document.createElement('td');
const Completed = document.createElement('td');
const record = document.createElement('td');
Total.textContent = totalflagamount.toString();
Completed.textContent = Rdata.Flags;
record.textContent = Completed.innerHTML + ' / ' + Total.innerHTML;
row.appendChild(Completed);
row.appendChild(Total);
row.appendChild(record);
console.log('TB2:', TB2);
console.log('row:', row);
TB2.appendChild(row);
}
else {
console.error('Error getting data for Flag Users');
}
}
// get a new name from name array
export function getNewName(): string {
let first = '';
let second = '';
// get what arrays to be used
let firstarr = Math.floor(Math.random() * 2);
let secondarr = Math.floor(Math.random() * 2);
// get the random array items based on the arrays used
if (firstarr === 0) first = Adjectives[Math.floor(Math.random() * Adjectives.length)];
else first = Colors[Math.floor(Math.random() * Colors.length)];
if (secondarr === 0) second = Animals[Math.floor(Math.random() * Animals.length)];
else second = Things[Math.floor(Math.random() * Things.length)];
// set up the name
Name = '';
Name += first + second;
// determine if there will be a random number after and add it
let num = Math.floor(Math.random() * 2);
if (num === 0) Name += Math.floor(Math.random() * 1001);
return Name;
}
// set the random name to the name of the user
export async function setNewName(NameInput: string): Promise<void> {
const name = NameInput;
const data = { name: name, email: getEmail() };
const res = await fetch('api/users/setUserName', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
}

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { TerminalService } from './terminal.service';
describe('TerminalService', () => {
let service: TerminalService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TerminalService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,80 @@
/*
Alex Miller
Jordan Latimer
Service file for Terminals
*/
import { Injectable } from '@angular/core';
import { Terminal } from '@xterm/xterm';
import { AttachAddon } from '@xterm/addon-attach';
import { getEmail } from './Helpers';
@Injectable({
providedIn: 'root'
})
export class TerminalService {
private term: Terminal; // terminal for container
private socket: WebSocket | null = null; // websocket attached to container
constructor() {
this.term = new Terminal();
}
// get the terminal
getTermainal() : Terminal {
return this.term;
}
// set the terminal
setTerminal(newTerm: Terminal) : void {
this.term = newTerm;
}
ClearTerminal() : void {
if(this.term){
this.term.write("\x1b[2J");
this.term.write("\x1b[H");
this.term.write('Loading Environment...');
}
}
// create terminal
CreateTerminal(): void {
const container = document.getElementById('Linux_Shell');
if (container && this.term) {
this.term.open(container);
this.CreateWebSocket();
}
}
// create the web socket connection
CreateWebSocket() {
this.socket = new WebSocket('/ws');
this.socket.onopen = () => {
if (this.socket) {
this.socket.send(JSON.stringify(getEmail()));
console.log('WebSocket opened');
setTimeout(() => {
const attachAddon = new AttachAddon(this.socket!);
this.term.loadAddon(attachAddon);
this.term.focus();
console.log('reattached attachAddon');
});
}
}
}
// reattach the terminal for a new flag
reattachTerminal() {
if (this.term) {
this.term.dispose();
this.term = new Terminal();
}
this.CreateTerminal(); // create the new terminal
}
}

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { TreeService } from './tree.service';
describe('TreeService', () => {
let service: TreeService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TreeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,117 @@
import { Injectable, InputSignal, Inject } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class TreeService {
private name: string;
private contents: string;
private children: TreeService[];
private parentnode: TreeService | null;
private directory: boolean;
private nodenum: number;
constructor(@Inject(String) name: string, @Inject(String) contents: string, @Inject(Boolean) directory: boolean, @Inject(Number) nodenum: number, @Inject(TreeService) parentnode: TreeService | null) {
this.name = name;
this.contents = contents;
this.children = [];
this.parentnode = parentnode;
this.directory = directory;
this.nodenum = nodenum;
}
// adding a child node to tree
AddChild(node: TreeService) {
this.children.push(node);
}
// chekc if a child already has the same name
IsNameTaken(filename: string): boolean {
// check children
if (this.children.length > 0) {
for (var i=0; i < this.children.length; i++) {
let child = this.children[i];
// check the name
if (child.name === filename) return true;
}
}
return false;
}
// get the node with the correct name and nodenum
getNodeByName(name: string, num: number): TreeService | null{
// base case
if (this.name === name && this.nodenum == num) return this;
// Traverse tree structure
if (this.children.length > 0) {
for (var i=0; i < this.children.length; i++) {
const childResult = this.children[i].getNodeByName(name,num);
if (childResult !== null && childResult.name === name && childResult.nodenum == num) {
return childResult;
}
}
}
return null;
}
// get all the nodes in the tree with the same name
getNodesSameName(name: string, nodes: TreeService[]): TreeService[] {
// base case
if (this.name === name) nodes.push(this);
// check children
if (this.children.length > 0) {
for (var i=0; i < this.children.length; i++) {
this.children[i].getNodesSameName(name,nodes);
}
}
return nodes;
}
// delete the parent references to send tree to server
DeleteParentRef() {
this.parentnode = null;
if (this.children.length > 0) {
for (var i=0; i < this.children.length; i++) {
this.children[i].DeleteParentRef();
}
}
}
// delete all children of this node
DeleteTheChildren() {
this.children = [];
}
// gets
getDir() : boolean {
return this.directory;
}
getName() : string{
return this.name;
}
getNodeNum() : number {
return this.nodenum;
}
getChildren() : TreeService[] {
return this.children;
}
getParent() : TreeService | null {
return this.parentnode;
}
// sets
setChildren(newchildren: TreeService[]) {
this.children = newchildren;
}
}

@ -0,0 +1,104 @@
html {
background-color: green;
}
body {
background-color: white;
}
form {
text-align: center;
}
h1, h3 {
text-align: center;
font-weight: bold;
}
#Inputs {
display: grid;
grid-template-columns: max-content max-content;
grid-gap: 5px;
margin-left: 25%;
margin-top: 25%;
}
label {
text-align: right;
}
#Submit {
justify-content: center;
align-items: center;
text-align: center;
}
#submit * {
width: 15%;
height: 5%;
}
#Submit button {
background-color: red;
font-weight: bold;
}
#hint1Text, #hint2Text, #hint3Text {
display: none;
}
/* Show the respective hint fields based on the selected radio button */
#hint1:checked ~ #hint1Text {
display: block;
}
#hint2:checked ~ #hint1Text,
#hint2:checked ~ #hint2Text {
display: block;
}
#hint3:checked ~ #hint1Text,
#hint3:checked ~ #hint2Text,
#hint3:checked ~ #hint3Text{
display: block;
}
/* for Create Image */
#body {
display: grid;
grid-template-rows: 25% auto auto auto auto auto 25%;
grid-template-columns: 20% 20% 20% 20% 20%;
justify-items: center;
}
#labels {
grid-row: 1/2;
grid-column: 3/4;
}
#Butts {
grid-row: 2/3;
grid-column: 3/4;
align-self: flex-end;
}
#TextBox {
grid-row: 3/6;
grid-column: 1/3;
}
textarea {
resize: none;
}
#DropBox {
grid-row: 3/6;
grid-column: 3/4;
border: 3px black dashed;
align-content: center;
justify-items: center;
}
#SendFile {
grid-row: 6/7;
grid-column: 3/4;
}
#FileTree {
grid-row: 3/6;
grid-column: 4/6;
}
#Submition {
grid-row: 7/8;
grid-column: 3/4;
}
.selected-node {
background-color: #f0f0f0;
font-weight: bold;
color: #333;
}

@ -0,0 +1,31 @@
<!--
Jordan Latimer
Alex Miller
Adding a contest screen for Admin
-->
<html>
<head>
<title> Add Contest</title>
</head>
<body>
<h1> Add Contest </h1>
<!-- form for inserting the contest -->
<form id="Inputs">
<label> Name: </label>
<input id="Name" type="text" align="middle" required>
<label> Description: </label>
<input id="Desc" type="text" align="middle" required>
</form>
<br>
<br>
<!-- submitting and closing -->
<div id="Submit">
<input id="AddContest" type="button" value="Add Contest" (click)="AddContest();">
<button onclick="self.close();"> Cancel </button>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddContestComponent } from './add-contest.component';
describe('AddContestComponent', () => {
let component: AddContestComponent;
let fixture: ComponentFixture<AddContestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AddContestComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AddContestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import { getEmail } from '../Helper/Helpers';
@Component({
selector: 'app-add-contest',
imports: [],
templateUrl: './add-contest.component.html',
styleUrl: './add-contest.component.css'
})
export class AddContestComponent {
// add a contest to the database
async AddContest() {
const nameInput = document.getElementById("Name") as HTMLInputElement;
const descInput = document.getElementById("Desc") as HTMLTextAreaElement;
const name = nameInput.value;
const desc = descInput.value;
console.log("Name entered: ", name);
console.log("Description entered: ", desc);
const isActive = 0;
console.log("EMAIL IS:", this.getEmail())
const data = {Name: name, IsActive: isActive, email: this.getEmail(), Desc: desc};
const res = await fetch('api/AddContest', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
console.log("Success: adding contest");
window.opener.location.reload();
window.close();
}
else {
console.log("ERROR: adding contest");
}
}
getEmail(): string{
return getEmail();
}
}

@ -0,0 +1,37 @@
/*
Alex Miller
Jordan Latimer
CSS for Add Flag popup window
*/
#FlagInputs {
text-align: center;
background-color: lightgreen;
padding: 10px;
}
h1 {
text-align: center;
}
#Submit {
text-align: center;
justify-items: center;
}
button {
font-weight: bold;
height: 15%;
width: 20%;
}
#flagName {
background-color: lawngreen;
}
select {
display: inline-block;
width: 170px;
margin: 10px;
font-weight: bold;
}
option {
text-align: center;
}

@ -0,0 +1,72 @@
<!--
Jordan Latimer
Alex Miller
Adding a Flag for Admin
-->
<html>
<head>
<title> Add Flag</title>
</head>
<body>
<h1> Add Flag </h1>
<!-- form for adding the flag -->
<form id="FlagInputs">
<label> Name: </label>
<input id="Name" type="text" align="middle" required>
<br><br>
<label> Description: </label>
<input id="Description" required>
<br><br>
<input type="radio" id="hint1" name="hintCount" value="1">
<label for="hint1">1 Hint</label>
<br>
<input type="radio" id="hint2" name="hintCount" value="2">
<label for="hint2">2 Hints</label>
<br>
<input type="radio" id="hint3" name="hintCount" value="3">
<label for="hint3">3 Hints</label>
<br><br>
<div id = "hint1Text">
<label for="Hint1">Hint 1: </label>
<input id="Hint1" type="text"><br><br>
</div>
<div id = "hint2Text">
<label for="Hint2">Hint 2: </label>
<input id="Hint2" type="text"><br><br>
</div>
<div id = "hint3Text">
<label for="Hint3">Hint 3: </label>
<input id="Hint3" type="text"><br><br>
</div>
<!-- image of the flag to create container -->
<label> Image: </label>
<select id="Images">
<option> ubuntu </option>
</select>
<br><br>
<!-- path to place the flag into -->
<label> Path To File </label>
<input id="Path" type="text">
<br>
<br>
<br>
<!-- submit button just closes the window for now -->
<div id="Submit">
<button id="flagName" type="submit" (click)="AddFlag()">Add Flag</button>
</div>
</form>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddFlagComponent } from './add-flag.component';
describe('AddFlagComponent', () => {
let component: AddFlagComponent;
let fixture: ComponentFixture<AddFlagComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AddFlagComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AddFlagComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,64 @@
import { Component } from '@angular/core';
import { gotoPage } from '../Helper/Helpers';
import { Router } from '@angular/router';
@Component({
selector: 'app-add-flag',
imports: [],
templateUrl: './add-flag.component.html',
styleUrl: './add-flag.component.css'
})
export class AddFlagComponent {
constructor(private router: Router){}
// add a flag to a contest
async AddFlag(){
// get contestId from URL
const contestId = parseInt(sessionStorage.getItem('selectedContestID') || '', 10);
// get all the values in the form
const flagName = document.getElementById("Name") as HTMLInputElement;
const description = document.getElementById("Description") as HTMLInputElement;
let Hint1 = document.getElementById("Hint1") as HTMLInputElement;
let Hint2 = document.getElementById("Hint2") as HTMLInputElement;
let Hint3 = document.getElementById("Hint3") as HTMLInputElement;
const image = document.getElementById('Images') as HTMLInputElement;
const path = document.getElementById('Path') as HTMLInputElement;
// required fields
if(!flagName.value){
alert("Enter a flag name.");
return;
}
if(contestId === null || contestId === undefined){
alert("Please select a contest first.");
return;
}
// add the flag
const data = {name: flagName.value, desc: description.value, contest: contestId, image: image.value, path: path.value, hint1: Hint1.value || '', hint2: Hint2.value || '', hint3: Hint3.value || ''};
try{
const response = await fetch('api/AddFlag', {
method: 'POST',
headers: {'Content-Type': 'application/json',},
body: JSON.stringify(data)
});
if(response.ok){
console.log("Flag added");
// reload the parent window when this window closes
window.opener.postMessage({ type: 'FLAG_ADDED', contestId: contestId }, window.origin);
window.close();
}
else{
alert("Failed to add flag.");
}
} catch(error){
console.error("Error adding flag:", error);
alert("An error occurred while adding flag.");
}
}
navtoPageCI(){
gotoPage(this.router, '/create-image');
}
}

@ -0,0 +1,104 @@
html {
background-color: green;
}
body {
background-color: white;
}
form {
text-align: center;
}
h1, h3 {
text-align: center;
font-weight: bold;
}
#Inputs {
display: grid;
grid-template-columns: max-content max-content;
grid-gap: 5px;
margin-left: 25%;
margin-top: 25%;
}
label {
text-align: right;
}
#Submit {
justify-content: center;
align-items: center;
text-align: center;
}
#submit * {
width: 15%;
height: 5%;
}
#Submit button {
background-color: red;
font-weight: bold;
}
#hint1Text, #hint2Text, #hint3Text {
display: none;
}
/* Show the respective hint fields based on the selected radio button */
#hint1:checked ~ #hint1Text {
display: block;
}
#hint2:checked ~ #hint1Text,
#hint2:checked ~ #hint2Text {
display: block;
}
#hint3:checked ~ #hint1Text,
#hint3:checked ~ #hint2Text,
#hint3:checked ~ #hint3Text{
display: block;
}
/* for Create Image */
#body {
display: grid;
grid-template-rows: 25% auto auto auto auto auto 25%;
grid-template-columns: 20% 20% 20% 20% 20%;
justify-items: center;
}
#labels {
grid-row: 1/2;
grid-column: 3/4;
}
#Butts {
grid-row: 2/3;
grid-column: 3/4;
align-self: flex-end;
}
#TextBox {
grid-row: 3/6;
grid-column: 1/3;
}
textarea {
resize: none;
}
#DropBox {
grid-row: 3/6;
grid-column: 3/4;
border: 3px black dashed;
align-content: center;
justify-items: center;
}
#SendFile {
grid-row: 6/7;
grid-column: 3/4;
}
#FileTree {
grid-row: 3/6;
grid-column: 4/6;
}
#Submition {
grid-row: 7/8;
grid-column: 3/4;
}
.selected-node {
background-color: #f0f0f0;
font-weight: bold;
color: #333;
}

@ -0,0 +1,38 @@
<!--
Alex Miller
Jordan Latimer
Adding a student page for Admin
-->
<html>
<head>
<title> Add Student </title>
</head>
<body>
<h1> Add Student </h1>
<!-- form for adding a student -->
<form id="Inputs">
<label> Name: </label>
<input id="Name" type="text" [(ngModel)]="Uname" name="Uname" align="middle">
<label> Email: </label>
<input id="EM" type="text" [(ngModel)]="email" name="email" align="middle">
<label> Password: </label>
<input *ngIf="!showPass" id="PS1" type="password" [(ngModel)]="password" name="password" align="middle">
<input *ngIf="showPass" id="PS1" type="text" [(ngModel)]="password" name="password" align="middle">
<label> Confirm Password: </label>
<input *ngIf="!showPass" id="PS2" type="password" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
<input *ngIf="showPass" id="PS2" type="text" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
<label> Show Password </label>
<input type="checkbox" (click)="ShowPassword()" id="ShowPass">
</form>
<br>
<br>
<!-- submitting or closing-->
<div id="Submit">
<input id="AddStudent" type="submit" (click)="AddStudent()">
<button onclick="self.close();"> Cancel </button>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddStudentComponent } from './add-student.component';
describe('AddStudentComponent', () => {
let component: AddStudentComponent;
let fixture: ComponentFixture<AddStudentComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AddStudentComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AddStudentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,59 @@
import { Component } from '@angular/core';
import { getEmail } from '../Helper/Helpers';
import { NgIf } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-add-student',
imports: [NgIf, FormsModule],
templateUrl: './add-student.component.html',
styleUrl: './add-student.component.css'
})
export class AddStudentComponent {
Uname: string = '';
email: string = '';
password: string = '';
confirmPassword: string = '';
showPass: boolean = false;
ShowPassword() {
this.showPass = !this.showPass
}
// add a student to the database
async AddStudent() {
const Aemail = getEmail();
// password protection
if (this.password.length < 10 || this.password == "") {
alert("password must be at least 10 characters");
return;
}
else if(this.email == Aemail){
alert("Funny guy, eh?");
return;
}
if (this.password == this.confirmPassword) { // passwords match
const data = {name: this.Uname, email: this.email, Aemail: Aemail, password: this.password};
const res = await fetch('api/AddStudent', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
console.log("Success: adding student");
window.opener.location.reload();
window.close();
}
else {
console.log("ERROR: adding student");
}
}
else {
alert("Passowrds do not match");
}
}
}

@ -0,0 +1,141 @@
/*
Jordan Latimer
Alex Miller
CSS for both Contest Pages
*/
body {
display: grid;
background-color: green;
/* columns and rows sizes */
grid-template-columns: 15% 10% auto 20%;
grid-template-rows: 10% 40% 10% auto;
}
/* Name of the contest at the top left */
#Contest_Name {
grid-row: 1/2;
grid-column: 1/3;
text-align: center;
font-weight: bold;
font-size: 175%;
}
#Contest_Name * {
margin: auto;
}
/* sidebar thats scrollable for different flags */
#Flag_Sidebar {
background-color: white;
grid-row: 2/5;
grid-column: 1/2;
height: 100%;
}
#Flag_Sidebar ul {
list-style-type: none;
height: 80%;
overflow-y: scroll;
}
.hidden {
background-color: white;
visibility: hidden;
}
/* details for each flag (name, description, etc) */
#Flag_Deats {
background-color: white;
grid-row: 2/3;
grid-column: 2/4;
text-align: center;
}
#Notifications {
background-color: rgba(65, 145, 55, 0.511);
margin-left: 50%;
grid-row: 3/4;
grid-column: 3/5;
display: none;
}
#Notifications.show {
display: block;
}
/* Submitting the flag */
#Submit_Flag {
background-color: white;
grid-row: 3/4;
grid-column: 2/4;
}
#Submit_Flag_Stuff * {
height: 40%;
width: 15%;
}
#Submit_Butt {
background-color: lawngreen;
font-weight: bold;
font-size: 100%;
}
#Submit_Flag h3 {
font-weight: bold;
text-align: left;
display: inline;
}
.Incorrect, .Correct {
font-weight: bold;
text-align: center;
font-size: 125%;
}
.Incorrect {
background-color: red;
color: white;
}
.Correct {
background-color: lawngreen;
color: black;
}
/* hints for each flag */
#Hints {
background-color: white;
grid-row: 2/4;
grid-column: 4/5;
text-align: center;
}
textarea {
background-color: white;
resize: none;
border: none;
outline: none;
text-align: center;
}
/* Linux Shell for commands */
#Linux_Shell {
grid-row: 4/5;
grid-column: 2/5;
color: lawngreen;
}
/* when a flag is selected */
.selected-flag {
background-color: lightgray;
font-weight: bold;
color: #333;
}
/* Flags and Hints Labels */
.FlagHint {
background-color: black;
color: white;
text-align: center;
margin: 10px;
font-size: 200%;
}

@ -0,0 +1,59 @@
<!--
Alex Miller
Jordan Latimer
Contest Page for Admins
-->
<html>
<head>
<title> NMU CTF Contest Page </title>
</head>
<body>
<!-- Bar at top for moving tabs -->
<div id="Taskbar">
<a (click)="confirmSelection()"> Profile </a>
</div>
<!-- Contest name -->
<div id="Contest_Name">
<h1 id="ContHeader"></h1>
</div>
<!-- Sidebar thats scrollable for different flags -->
<div id="Flag_Sidebar">
<h2 class="FlagHint">Flags</h2>
<ul id="FlagList">
</ul>
</div>
<!-- details for each flag (name, description etc) -->
<div id="Flag_Deats">
<div id ="DefaultMessage">
<h2>Welcome to NMU CTF</h2>
<p>There are currently no contests active at this time</p>
</div>
<h2 id="ContName"></h2>
<p id="ContDesc"></p>
<h2 id="Flag_Name"></h2>
<p id="Desc" ></p>
</div>
<div id="Submit_Flag">
</div>
<!-- Hints for each flag -->
<div id="Hints">
<div id="Hint_Stuff">
<h2 class="FlagHint"> Hints </h2>
<div id="Hint_Butts">
</div>
<br>
<br>
<textarea readonly rows="15" cols="30" id="Hint_Desc"></textarea>
</div>
</div>
<!-- Linux Shell for commands -->
<div id="Linux_Shell"></div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminContestComponent } from './admin-contest.component';
describe('AdminContestComponent', () => {
let component: AdminContestComponent;
let fixture: ComponentFixture<AdminContestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AdminContestComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AdminContestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,39 @@
import { Component, ElementRef, Renderer2 } from '@angular/core';
import { gotoPage, getEmail } from '../Helper/Helpers';
import { Router } from '@angular/router';
import { loadPage } from '../Helper/ContestSetup';
import { TerminalService } from '../Helper/terminal.service';
@Component({
selector: 'app-admin-contest',
imports: [],
templateUrl: './admin-contest.component.html',
styleUrl: './admin-contest.component.css'
})
export class AdminContestComponent {
constructor(private renderer: Renderer2, private el: ElementRef, private router: Router, private terminalService: TerminalService){}
ngOnInit(): void {
// attach to terminal onload
this.terminalService.reattachTerminal();
loadPage(this.renderer, this.el, this);
}
confirmSelection(){
const confirmSelection_ = confirm("You will lose your current flag progress, are you sure?");
if(confirmSelection_){
gotoPage(this.router, '/admin-profile');
}
else
console.log("Profile navigation canceled");
}
// clear and set terminal for new flag
async ClearAndSetTerminal() {
await this.terminalService.ClearTerminal();
await this.terminalService.reattachTerminal();
}
}

@ -0,0 +1,164 @@
/*
Alex Miller
Jordan Latimer
CSS for Profile Page on Admin side
*/
body {
display: grid;
/* columns and rows sizes */
grid-template-columns: auto auto auto auto auto;
grid-template-rows: auto auto auto auto auto auto;
background-color: lightgreen;
}
/* Header */
#Header {
grid-row: 1/3;
grid-column: 1/6;
margin: 50px;
text-align: center;
background-color: green;
}
.HeadButts {
height: 25px;
width: 150px;
margin: 5px;
}
#ModifyContests {
background-color: blue;
color: white;
}
#Logout {
background-color: red;
}
#ContestPage {
background-color: lawngreen;
}
/* Student side of the page */
#Students {
grid-row: 4/6;
grid-column: 1/3;
margin: 50px;
height: 500px;
text-align: center;
background-color: green;
}
#Student_Search input, select, button {
margin: 5px;
}
.StudentButts, .FlagButts, select {
width: 23%;
height: 25px;
text-align: center;
}
#SortButt {
width: 10%;
background-color: blue;
color: white;
}
#StudentsTable, #FlagsTable {
margin: 10px;
height: 75%;
overflow-y: scroll;
}
/* Flag side of the page */
#Flags {
grid-row: 4/6;
grid-column: 4/6;
margin: 50px;
text-align: center;
background-color: green;
}
#ContestDropDown button, select {
margin: 5px;
}
/* Popup for deleting students */
#DialogBox {
text-align: center;
top: 50%;
transform: translate(-50%, -50%);
padding: 100px;
z-index: 100;
}
#DialogBox #Delete {
background-color: red;
font-weight: bold;
}
#DialogBox button {
height: 25px;
width: 75px;
}
/* buttons */
#AddStudent {
background-color: lawngreen;
}
#EditStudent {
background-color: blue;
color: white;
width: 50%;
}
#DeleteStudent {
background-color: red;
}
/* tooltips */
.tooltip {
font-weight: bold;
color: white;
display: inline;
}
.tooltip:hover .tooltipText {
visibility: visible;
}
.tooltip .tooltipText {
visibility: hidden;
height: auto;
background-color: gray;
color: black;
text-align: center;
position: absolute;
padding: 5px;
z-index: 100;
}
/* no ID stuff */
table {
width: 100%;
border: 3px solid black;
border-collapse: seperate;
border-spacing: 0;
background-color: white;
}
table thead {
top: 0;
position: sticky;
position: -webkit-sticky;
z-index: 1;
background-color: black;
color: white;
}
td {
border: 3px solid black;
text-align: center;
height: 50px;
}
button {
width: 80%;
height: 50%;
font-weight: bold;
text-align: center;
}
h1 {
display: inline;
font-weight: bold;
}

@ -0,0 +1,169 @@
<!--
Alex Miller
Jordan Latimer
Profile Page for Admins
-->
<html>
<head>
<title> NMU CTF Profile Page </title>
</head>
<body>
<div id="Header">
<h1> Profile </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can do anything and
<br>
everything with your <span style="color: white">Students</span>
or look at the current stats for <span style="color: white;">Flags</span>
</span>
</div>
<br>
<br>
<button class='HeadButts' id="ContestPage" (click)="navtoPageAC()">Back to Contest</button>
<button class='HeadButts' id="ModifyContests" (click)="navtoPageMC()">Modify Contests</button>
<button class='HeadButts' id="Logout" (click)="onLogout()">Logout</button>
<br>
<br>
</div>
<!-- everything with students side of the page -->
<div id="Students">
<h1> Students </h1>
<div class="tooltip">
?
<span class="tooltipText">
Here you will see a list of your <span style="color:white">Students</span>
<br>
you can search for specific students
<br>
or filter them how you would like. For searching a specific <span style="color:white;">Student</span>, make sure
<br>
<span style="color:white;">Name</span> is selected in the dropdown
</span>
</div>
<br>
<br>
<!-- searching for students -->
<div id="Student_Search">
<input id="StudentName" type="text" placeholder="Student Name" [(ngModel)]="searchText">
<select id="Options">
<option disabled selected hidden> Sort By: </option>
<option value="Name (A->Z)"> Name (A->Z) </option>
<option value="Name (Z->A)"> Name (Z->A) </option>
<option value="Flags (H->L)"> Flags (H->L) </option>
<option value="Flags (L->H)"> Flags (L->H) </option>
</select>
<button class="StudentButts" id="SortButt" (click)="sortTable()">Sort</button>
<button class="StudentButts" id="AddStudent" (click)="openAddUserPopup()"> Add Student </button>
<div class="tooltip">
?
<span class="tooltipText">
<span style="color:lawngreen;">Add Student: </span> opens a popup to add a <span style="color:white;">Student</span> to the list
<br>
<span style="color:blue;">Edit: </span> allows you to edit a students <span style="color:white;">Password</span>
<br>
<span style="color:red;">Delete: </span> Deletes a <span style="color:white;">Student</span>
</span>
</div>
</div>
<!-- Student Table -->
<div id="StudentsTable">
<table>
<thead>
<tr>
<th> Student Name </th>
<th> Flags </th>
<th> Email </th>
<th> Change Password </th>
<th> Delete </th>
</tr>
</thead>
<tbody id="TBody">
<tr *ngFor="let user of users">
<td>{{ user.Name }}</td>
<td>{{ user.Flags }}</td>
<td>{{ user.Email }}</td>
<td><button id="EditStudent" (click)="openPasswordPopup(user.Email)">Edit</button></td>
<td><button id="DeleteStudent" (click)="openDeleteDialog(user.Email)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- everythig with the flags side of the page -->
<div id="Flags">
<h1> Flags </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can look at how your <span style="color:white;">Students</span>
<br>
are doing with the <span style="color:white;">Flags</span> you created
</span>
</div>
<br>
<br>
<!-- dropdown for contests -->
<div id="ContestDropDown">
<select id="DropDown" [(ngModel)]="selectedContest">
<option disabled selected hidden> Select a Contest </option>
<option *ngFor="let contest of contests" [value] ="contest.Name">{{ contest.Name }}</option>
</select>
<button class="FlagButts" (click)="populateFlagsTable()">Get Flags</button>
<button class="FlagButts" (click)="openLeaderboardPopup(getEmail())">Leaderboard</button>
<div class="tooltip">
?
<span class="tooltipText">
<span style="color:white;">Get Flags: </span> shows all the <span style="color:white;">Flags</span> for that contests and the stats
<br>
<span style="color:white;">Leaderboard: </span> popup a leaderboard for that <span style="color:white;">Contest</span> to see how each <span style="color:white;">Student</span> is doing
</span>
</div>
</div>
<!-- table for flags -->
<div id="FlagsTable">
<table>
<thead>
<tr>
<th> Flag Name </th>
<th> Attempts </th>
<th> Completions </th>
<th> Ratio </th>
</tr>
</thead>
<tbody id="FlagTBody">
<tr *ngFor="let flag of flags">
<td>{{ flag.Name }}</td>
<td>{{ getAttempts(flag.FlagID )}}</td>
<td>{{ getCompletions(flag.FlagID) }}</td>
<td [ngStyle]="{ color: getRatioColor(flag.FlagID) }"> {{ getRatio(flag.FlagID) }}%</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Dialog box for reassurance of deleting a student -->
<dialog id="DialogBox">
Are you sure you want to delete <strong id="emailDB">{{ emailToDelete }}</strong>
<br>
<br>
<button (click)="closeDialog()"> Cancel </button>
<br>
<br>
<button id="Delete" (click)="deleteUser()"> Delete </button>
</dialog>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminProfileComponent } from './admin-profile.component';
describe('AdminProfileComponent', () => {
let component: AdminProfileComponent;
let fixture: ComponentFixture<AdminProfileComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AdminProfileComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AdminProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,179 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { gotoPage, logOut, getEmail, Popup } from '../Helper/Helpers';
import { AdminProfileService } from './admin-profile.service';
import { Flag } from '../models/flag.model';
import { User } from '../models/user.model';
import { Contest } from '../models/contest.model';
import { Submission } from '../models/submission.model';
import { NgFor } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-admin-profile',
imports: [NgFor, FormsModule, CommonModule],
templateUrl: './admin-profile.component.html',
styleUrl: './admin-profile.component.css'
})
export class AdminProfileComponent implements OnInit{
users: User[] = []
displayedUsers: User[] = []
contests: Contest[] = []
subs: Submission[] = []
flags: Flag[] = []
selectedContest: string = '';
sortOption: string = '';
constructor(private router: Router, private adminProfileService: AdminProfileService){}
emailToDelete = '';
searchText: string = '';
ngOnInit(){
this.getContests();
this.populateTable();
}
async populateTable(){
try{
const email = this.getEmail();
const users = await this.adminProfileService.getAllUsers(email);
this.users = users;
} catch(error) {
console.error('Error populating user table', error);
}
}
async populateFlagsTable(): Promise<void> {
if(!this.selectedContest){
alert('Must select a contest');
return;
}
try{
const email = getEmail();
const result = await this.adminProfileService.getContestFlagsSubs(email, this.selectedContest);
this.flags = result.flags;
this.subs = result.subs;
}catch(error){
console.error('Failed to populate flag table');
}
}
getAttempts(flagId: number): number{
return this.subs.filter(sub => sub.FlagID === flagId).reduce((acc, sub) => acc + sub.Attempts, 0);
}
getCompletions(flagId: number): number{
return this.subs.filter(sub => sub.FlagID === flagId).reduce((acc, sub) => acc + (sub.IsCorrect ? 1 : 0), 0);
}
getRatio(flagId: number): number{
const attempts = this.getAttempts(flagId);
const completions = this.getCompletions(flagId);
return attempts > 0 ? +(completions / attempts * 100).toFixed(2) : 0;
}
getRatioColor(flagId: number): string{
const ratio = this.getRatio(flagId);
if(ratio >= 80) return 'lawngreen';
else if (ratio >= 60) return 'orange';
return 'red';
}
openPasswordPopup(email: string){
sessionStorage.setItem('selectedUser', email);
Popup('/edit-student');
}
openAddUserPopup(){
Popup('/add-student');
}
openLeaderboardPopup(email: string){
Popup('Leaderboard.html');
}
async getContests(){
try {
const email = this.getEmail();
const contests = await this.adminProfileService.getContests(email);
this.contests = contests;
} catch(error){
console.error("Error getting contests", error);
}
}
openDeleteDialog(email: string): void {
this.emailToDelete = email;
const dialog = document.getElementById('DialogBox') as HTMLDialogElement
if(dialog){
dialog.showModal();
}
}
async deleteUser(){
try{
if(!this.emailToDelete) return;
await this.adminProfileService.deleteUser(this.emailToDelete);
console.log("User deleted successfully");
this.closeDialog();
this.populateTable();
} catch(error) {
console.error("Error deleting user", error);
}
}
closeDialog(): void {
const dialog = document.getElementById('DialogBox') as HTMLDialogElement;
if(dialog)
dialog.close();
this.emailToDelete = '';
}
sortTable(): void{
const name = this.searchText.trim().toLowerCase();
const option = this.sortOption;
let sorted = [...this.users];
if((option === 'Name (A->Z)' || option === 'Name (Z->A)') && name !== ''){
const targetIndex = sorted.findIndex(s => s.Name.toLowerCase() === name);
if(targetIndex !== -1){
const student = sorted.splice(targetIndex, 1)[0];
sorted.unshift(student);
}
else{
alert('Student is not in database');
}
}
if(option === 'Name (A->Z)'){
sorted.sort((a, b) => a.Name.localeCompare(b.Name));
}
else if(option === 'Name (Z->A)'){
sorted.sort((a, b) => b.Name.localeCompare(a.Name));
}
else if(option === 'Flags (H->L)'){
sorted.sort((a, b) => b.Flags - a.Flags);
}
else if(option === 'Flags (L->H)'){
sorted.sort((b, a) => a.Flags - b.Flags);
}
this.displayedUsers = sorted;
}
navtoPageAC(): void{
gotoPage(this.router, '/admin-contest');
}
navtoPageMC(): void{
gotoPage(this.router, '/modify-contest');
}
onLogout(): void{
logOut(this.router);
}
getEmail(): string{
return getEmail();
}
}

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AdminProfileService } from './admin-profile.service';
describe('AdminProfileService', () => {
let service: AdminProfileService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AdminProfileService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,90 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AdminProfileService {
constructor() { }
async getAllUsers(email: string): Promise<any> {
try {
const response = await fetch('api/users/getAllUsers', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error('Failed to fetch users');
}
return await response.json();
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
}
async deleteUser(email: string): Promise<any> {
try {
const response = await fetch('api/users/DeleteStudent', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error('Failed to delete user');
}
return await response.json();
} catch (error) {
console.error('Error deleting user:', error);
throw error;
}
}
async getContests(email: string): Promise<any> {
try {
const response = await fetch('api/contests/getContests', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error('Failed to fetch contests');
}
return await response.json();
} catch (error) {
console.error('Error fetching contests:', error);
throw error;
}
}
async getContestFlagsSubs(email: string, contest: string): Promise<any> {
try {
const response = await fetch('api/getContestFlagsSubs', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, contest })
});
if (!response.ok) {
throw new Error('Failed to fetch contest flags');
}
return await response.json();
} catch (error) {
console.error('Error fetching contest flags:', error);
throw error;
}
}
}

@ -0,0 +1 @@
<router-outlet></router-outlet>

@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'CTF-Frontend' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('CTF-Frontend');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, CTF-Frontend');
});
});

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'CTF-Frontend';
constructor(private router: Router){}
}

@ -0,0 +1,8 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
};

@ -0,0 +1,131 @@
import { NgModule } from '@angular/core';
import { AuthGuard } from './guards/auth.guard';
import { AdminGuard } from './guards/AdminGuard.guard';
import { UserGuard } from './guards/UserGuard.guard';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { UserMenuComponent } from './user-menu/user-menu.component';
import { UserProfileComponent } from './user-profile/user-profile.component';
import { ContestPageComponent } from './contest-page/contest-page.component';
import { UserProfileACComponent } from './user-profile-ac/user-profile-ac.component';
import { AdminContestComponent } from './admin-contest/admin-contest.component';
import { AdminProfileComponent } from './admin-profile/admin-profile.component';
import { LeaderboardComponent } from './leaderboard/leaderboard.component';
import { ModifyContestComponent } from './modify-contest/modify-contest.component';
import { AddContestComponent } from './add-contest/add-contest.component';
import { AddStudentComponent } from './add-student/add-student.component';
import { EditStudentComponent } from './edit-student/edit-student.component';
import { AddFlagComponent } from './add-flag/add-flag.component';
import { UNAUTHORIZEDComponent } from './unauthorized/unauthorized.component';
import { CreateImageComponent } from './create-image/create-image.component';
export const routes: Routes = [
{
path:'',
component: LoginComponent,
title: 'Login',
},
{
path: 'user-menu',
component: UserMenuComponent,
title: 'User Menu',
canActivate: [AuthGuard, UserGuard]
},
{
path: 'user-profile',
component: UserProfileComponent,
title: 'User Profile',
canActivate: [AuthGuard, UserGuard]
},
{
path: 'contest-page',
component: ContestPageComponent,
title: 'Contest Page',
canActivate: [AuthGuard, UserGuard]
},
{
path: 'user-profileAC',
component: UserProfileACComponent,
title: 'User Profile',
canActivate: [AuthGuard, UserGuard]
},
{
path: 'admin-contest',
component: AdminContestComponent,
title: 'Admin Contest',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'admin-profile',
component: AdminProfileComponent,
title: 'Admin Profile',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'leaderboard',
component: LeaderboardComponent,
title: 'Leaderboard'
},
{
path: 'modify-contest',
component: ModifyContestComponent,
title: 'Modify Contest',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'add-contest',
component: AddContestComponent,
title: 'Add Contest',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'add-student',
component: AddStudentComponent,
title: 'Add Student',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'edit-student',
component: EditStudentComponent,
title: 'Edit Student',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'add-flag',
component: AddFlagComponent,
title: 'Add Flag',
canActivate: [AuthGuard, AdminGuard]
},
{
path: 'unauthorized',
component: UNAUTHORIZEDComponent,
title: 'You think youre slick eh?'
},
{
path: 'create-image',
component: CreateImageComponent,
title: 'Create Image'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule{}

@ -0,0 +1,157 @@
/*
Jordan Latimer
Alex Miller
CSS for both Contest Pages
*/
body {
display: grid;
background-color: green;
/* columns and rows sizes */
grid-template-columns: 15% 10% auto 20%;
grid-template-rows: 10% 40% 10% auto;
}
/* Name of the contest at the top left */
#Contest_Name {
grid-row: 1/2;
grid-column: 1/3;
text-align: center;
font-weight: bold;
font-size: 175%;
}
#Contest_Name * {
margin: auto;
}
/* sidebar thats scrollable for different flags */
#Flag_Sidebar {
background-color: white;
grid-row: 2/5;
grid-column: 1/2;
height: 100%;
}
#Flag_Sidebar ul {
list-style-type: none;
height: 80%;
overflow-y: scroll;
}
.hidden {
background-color: white;
visibility: hidden;
}
/* details for each flag (name, description, etc) */
#Flag_Deats {
background-color: white;
grid-row: 2/3;
grid-column: 2/4;
text-align: center;
}
#notificationMessage {
background-color: rgba(65, 145, 55, 0.511);
margin-left: 50%;
grid-row: 3/4;
grid-column: 3/5;
}
.notificationMessage {
background-color: rgba(65, 145, 55, 0.511);
margin-left: 50%;
grid-row: 3/4;
grid-column: 3/5;
}
#Notifications.show {
display: block;
}
.notification-banner {
background-color: rgba(65, 145, 55, 0.511);
margin-left: 50%;
grid-row: 3/4;
grid-column: 3/5;
border-radius: 5px;
text-align: center;
font-weight: bold;
}
/* Submitting the flag */
#Submit_Flag {
background-color: white;
grid-row: 3/4;
grid-column: 2/4;
}
#Submit_Flag_Stuff * {
height: 40%;
width: 15%;
}
#Submit_Butt {
background-color: lawngreen;
font-weight: bold;
font-size: 100%;
}
#Submit_Flag h3 {
font-weight: bold;
text-align: left;
display: inline;
}
.Incorrect, .Correct {
font-weight: bold;
text-align: center;
font-size: 125%;
}
.Incorrect {
background-color: red;
color: white;
}
.Correct {
background-color: lawngreen;
color: black;
}
/* hints for each flag */
#Hints {
background-color: white;
grid-row: 2/4;
grid-column: 4/5;
text-align: center;
}
textarea {
background-color: white;
resize: none;
border: none;
outline: none;
text-align: center;
}
/* Linux Shell for commands */
#Linux_Shell {
grid-row: 4/5;
grid-column: 2/5;
color: lawngreen;
}
/* when a flag is selected */
.selected-flag {
background-color: lightgray;
font-weight: bold;
color: #333;
}
/* Flags and Hints Labels */
.FlagHint {
background-color: black;
color: white;
text-align: center;
margin: 10px;
font-size: 200%;
}

@ -0,0 +1,75 @@
<!--
Alex Miller
Jordan Latimer
Contest Page for Users
-->
<html>
<head>
<title> NMU CTF Contest Page </title>
</head>
<body>
<!-- Bar at top for moving tabs, this is for the practice contest -->
<ng-container *ngIf="isPractice">
<a href="#" (click)="confirmSelection(1); $event.preventDefault()">Leave Practice</a>
</ng-container>
<!-- Show if NOT Admin and NOT in Practice -->
<ng-container id="Taskbar" *ngIf="!isPractice && !isAdmin">
<a id="Current_Page" href="#" (click)="navtoPageUM(); $event.preventDefault()">Contest</a>
<a href="#" (click)="confirmSelection(2); $event.preventDefault()">Profile</a>
</ng-container>
<!-- Contest name -->
<div id="Contest_Name">
<h1 id="ContHeader"></h1>
</div>
<!-- Sidebar thats scrollable for different flags -->
<div id="Flag_Sidebar">
<h2 class="FlagHint"> Flags </h2>
<ul id="FlagList">
</ul>
</div>
<!-- details for each flag (name, description etc) -->
<div id="Flag_Deats">
<div id ="DefaultMessage">
<h2>Welcome to NMU CTF</h2>
<p>There are currently no contests active at this time</p>
</div>
<h2 id="ContName"></h2>
<p id="ContDesc"></p>
<h2 id="Flag_Name"></h2>
<p id="Desc"></p>
</div>
<!-- Submitting the flag -->
<div id="Submit_Flag">
<div id="Submit_Flag_Stuff">
<h3> Submit Flag: </h3>
<input type="text" placeholder="NMUCTF${FLAG}">
<input id="Submit_Butt" type="submit" value="Submit" (click)="onSubmission()">
<p id="Submission_Result"></p>
</div>
</div>
<div *ngIf="notificationMessage" id="notificationMessage" class="notificationMessage">
{{ notificationMessage }}
</div>
<!-- Hints for each flag -->
<div id="Hints">
<div id="Hint_Stuff">
<h2 class="FlagHint"> Hints </h2>
<div id="Hint_Butts">
</div>
<br>
<br>
<textarea readonly rows="15" cols="30" id="Hint_Desc"></textarea>
</div>
</div>
<!-- Linux Shell for commands -->
<div id="Linux_Shell"></div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContestPageComponent } from './contest-page.component';
describe('ContestPageComponent', () => {
let component: ContestPageComponent;
let fixture: ComponentFixture<ContestPageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContestPageComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ContestPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,88 @@
import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { NgIf } from '@angular/common';
import { Router } from '@angular/router'
import { loadPage, handleSubmission, getAdmin } from '../Helper/ContestSetup';
import { getEmail, gotoPage } from '../Helper/Helpers';
import { TerminalService } from '../Helper/terminal.service';
import { SocketIOService } from '../notifications/socket-io.service';
@Component({
selector: 'app-contest-page',
imports: [NgIf],
templateUrl: './contest-page.component.html',
styleUrl: './contest-page.component.css'
})
export class ContestPageComponent implements OnInit {
isPractice = window.location.href.includes("Prac");
isAdmin: boolean = this.isItAdmin();
notificationMessage: string | null = null;
constructor(private renderer: Renderer2,
private el: ElementRef,
private router: Router,
private terminalService: TerminalService,
private socketService: SocketIOService) {}
ngOnInit(): void {
// attach to terminal onload
this.terminalService.reattachTerminal();
//this.socketService.connect();
loadPage(this.renderer, this.el, this);
/*this.socketService.listen('submission-notification').subscribe((data: any) => {
console.log('Received notification:', data);
this.notificationMessage = `${data.email} solved ${data.flagName}!`;
console.log('Updated notif:', this.notificationMessage);
setTimeout(() => {
this.notificationMessage = null;
}, 5000);
});*/
this.socketService.listen('test-event').subscribe((data: any) => {
console.log('Test event received:', data);
this.notificationMessage = 'Test event received';
});
this.socketService.emit('test-event', { message: 'Hello from client' });
}
// clear and set terminal for new flag
async ClearAndSetTerminal() {
await this.terminalService.ClearTerminal();
await this.terminalService.reattachTerminal();
}
onSubmission(): void{
handleSubmission(this.el, this.socketService);
}
confirmSelection(num: number){
const confirmSelection_ = confirm("You will lose your current flag progress, are you sure?");
if(confirmSelection_){
const email = getEmail();
if(num === 1)
gotoPage(this.router, '/user-menu');
else
gotoPage(this.router, '/user-profileAC');
}
else
console.log("Profile navigation canceled");
}
navtoPageA(): void {
gotoPage(this.router, '/contest-page');
}
navtoPageUM(): void{
gotoPage(this.router, '/user-menu')
}
isItAdmin(): boolean{
if(getAdmin() === "True")
return true;
else
return false;
}
}

@ -0,0 +1,99 @@
/*
Alex Miller
Jordan Latimer
CSS for Create image
*/
#body {
display: grid;
grid-template-columns: 30% 30% auto;
grid-template-rows: auto auto auto auto auto;
background-color: lightgreen;
text-align: center;
}
/* Header */
#HeaderDiv {
grid-row: 1/2;
grid-column: 1/4;
background-color: green;
margin: 10px;
}
/* form stuff like image name, file name, add file etc */
#FormDiv {
grid-row: 2/3;
grid-column: 1/4;
background-color: green;
margin: 10px;
}
#FormStuff {
margin: 10px;
text-align: center;
background-color: white;
}
/* Contents of the File */
#FileContentsDiv {
grid-row: 3/6;
grid-column: 1/2;
background-color: green;
margin: 10px;
}
#FileContents {
margin-bottom: 10px;
}
/* drop box to drop files */
#DropBoxDiv {
grid-row: 3/6;
grid-column: 2/3;
background-color: green;
margin: 10px;
}
#DropBox {
border: 3px black dashed;
align-content: center;
justify-items: center;
background-color: white;
margin: 10px;
height: 310px;
}
/* file tree visual */
#FileTreeDiv {
grid-row: 3/6;
grid-column: 3/4;
background-color: green;
margin: 10px;
text-align: center;
}
#FileTree {
background-color: white;
margin: 10px;
height: 310px;
overflow-y: scroll;
overflow-x: scroll;
text-align: left;
}
textarea {
resize: none;
}
.Green {
background-color: lawngreen;
}
.Blue {
background-color: blue;
color: white;
height: 25%;
}
button {
font-weight: bold;
width: 10%;
height: 25%;
margin: 10px;
}

@ -0,0 +1,70 @@
<!--
Alex Miller
Jordan Latimer
Page for creating images for flags
-->
<html>
<head>
<title> Create Image </title>
</head>
<body id="body">
<!-- Header -->
<div id="HeaderDiv">
<h1> Create Image </h1>
<button class="Blue" (click)="navtoPageMC()">Back to Modify</button>
<br>
<br>
</div>
<!-- File Contents -->
<div id="FileContentsDiv">
<h1> Contents of File </h1>
<div id="FileContents">
<div id="TextBox">
<textarea id="filecontents" placeholder="Enter contents of files here" cols="40" rows="20"></textarea>
</div>
</div>
</div>
<!-- Form for adding files and directories -->
<div id="FormDiv">
<h1> File and Image </h1>
<div id="FormStuff">
<label> Image Name: </label>
<input id="ImageName" type="text" placeholder="image name">
<br>
<br>
<label> Name: </label>
<input id="filename" type="text" placeholder="Folder or File name">
<br>
<br>
<button class="Green" (click)="getFile(true)"> Add Folder</button>
<button class="Green" (click)="getFile(false)"> Add File </button>
<button (click)="DeleteFile()" style="background-color: red;"> Delete </button>
<br>
<br>
<button class="Green" (click)="CreateImage()" type="submit">Create Image</button>
</div>
</div>
<!-- Drop Box -->
<div id="DropBoxDiv">
<h1> Drop Box </h1>
<div id="DropBox">
<p> Drag and Drop files here or: </p>
<input type="file" id="FileInput" multiple (change)="onFileChange($event)">
</div>
</div>
<!-- File Tree -->
<div id="FileTreeDiv">
<h1> File Tree </h1>
<div id="FileTree">
</div>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateImageComponent } from './create-image.component';
describe('CreateImageComponent', () => {
let component: CreateImageComponent;
let fixture: ComponentFixture<CreateImageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CreateImageComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CreateImageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,314 @@
import { Component } from '@angular/core';
import { TreeService } from '../Helper/tree.service';
import { getEmail, gotoPage } from '../Helper/Helpers';
import { Router } from '@angular/router';
@Component({
selector: 'app-create-image',
imports: [],
templateUrl: './create-image.component.html',
styleUrl: './create-image.component.css'
})
export class CreateImageComponent {
private root: TreeService = new TreeService("Root", "", true, 1, null);
private allFiles: File[] | null = null;
constructor(private router: Router) {}
// initialize
ngOnInit() : void {
// create the root and refresh the file tree
this.RefreshFileTree();
}
RefreshFileTree() {
const filetree = document.getElementById('FileTree') as HTMLUListElement;
filetree.innerHTML = '';
// create list and add the root
const list = document.createElement('ul');
// go through full tree
const fulllist = this.AddListItem(list, this.root);
filetree.appendChild(fulllist);
this.ClearInputs();
}
// uploading files through dropbox
onFileChange(event: any) {
const files: FileList = event.target.files;
if (files.length > 0) {
if (!this.allFiles) this.allFiles = []; // set the array up if it's null
for (let i=0; i < files.length; i++) {
this.allFiles?.push(files[i]);
// add to tree
if (this.AddFileToTree(files[i], false, "", "")) return;
}
// reset files inside dropbox?
this.RefreshFileTree();
this.ClearInputs();
}
}
// add the file to the tree
AddFileToTree(file: File | null, dir: boolean, filename: string, contents: string) : boolean{
const elements = document.querySelectorAll('.selected-node');
// make sure a parent node is selected
if (elements.length == 0) {
alert('Must select a Directory');
this.ClearInputs();
return true;
}
// make sure all the nodes selected are directories
for (let i=0; i < elements.length; i++) {
let li = elements[i] as HTMLLIElement;
if (li.firstChild?.textContent?.substring(0,4) !== "(D)-") {
alert('Must select a directory');
this.ClearInputs();
return true;
}
}
// loop through all the elements adding the name of the file to the tree
for (let i=0; i < elements.length; i++) {
// get the parent node selected
let li = elements[i] as HTMLLIElement;
let results = this.getNodeNumFromClass(li);
let parentname: string = (results[0]).toString();
let parentnum: number = Number(results[1]);
let node = this.root.getNodeByName(parentname,parentnum);
// if the parent node isnt a directory, dont add it
if (node?.getDir() == false) {
alert('can only add files/folders to directories');
this.ClearInputs();
return true;
}
// make sure the filename isnt taken already
if (!node?.IsNameTaken(filename)) {
// get the number of nodes of this name for nodenum
let nodes: TreeService[] = [];
nodes = this.root.getNodesSameName(filename, nodes);
let nodenum = nodes.length + 1;
// add the node to the tree
if (file == null) { // not using dropbox
let newFile = new TreeService(filename, contents, dir, nodenum, node);
node?.AddChild(newFile);
}
else { // using dropbox
let FileObjName : string = file?.name!;
let newFile = new TreeService(FileObjName, "", false, nodenum, node);
node?.AddChild(newFile);
}
}
else {
alert('That file name is already under this directory');
this.ClearInputs();
return true;
}
}
return false;
}
// get the file stuff through creating a new file
getFile(dir: boolean) {
let nameElement = document.getElementById('filename') as HTMLInputElement;
let filename: string = nameElement.value;
let contentsElement = document.getElementById('filecontents') as HTMLTextAreaElement;
let contents : string = contentsElement.value;
if(this.AddFileToTree(null, dir, filename, contents)) return;
this.RefreshFileTree();
}
// use recursion to go through the tree adding each child
AddListItem(list: HTMLUListElement, node: TreeService) {
// add node to current list
const nodeitem = document.createElement('li');
const spanitem = document.createElement('span');
if (node.getDir()) spanitem.textContent = '(D)-' + node.getName();
else spanitem.textContent = node.getName();
// add the name and nodenum to the class list
spanitem.classList.add(node.getName() + node.getNodeNum());
spanitem.addEventListener('click', function(nodeitem) {
const listitem = nodeitem.target as HTMLLIElement;
listitem?.classList.toggle('selected-node');
});
nodeitem.appendChild(spanitem);
// add to list
list.appendChild(nodeitem);
// if node has children
if (node.getChildren().length > 0) {
let nodechildren = node.getChildren();
const childlist = document.createElement('ul');
for (var i=0; i < nodechildren.length; i++) {
let child = nodechildren[i];
list.appendChild(this.AddListItem(childlist, child));
}
}
// return the whole list
return list;
}
// get nodenum from class list
getNodeNumFromClass(item : HTMLLIElement) {
// get the class list of the selected item
const WholeClassList = item.classList;
const namenum = WholeClassList[0];
// break up the string into the two parts
const index = namenum.search(/\d/);
const name = namenum.substring(0,index);
let num : number = Number(namenum.substring(index));
const results = [name,num];
return results;
}
// delete a file from the tree
DeleteFile() {
const items = document.querySelectorAll('.selected-node');
// go through each item selected and remove it from parent
for (var i=0; i < items.length; i++) {
let li = items[i] as HTMLLIElement;
// get the name and num from class list
let results = this.getNodeNumFromClass(li);
let name : string = results[0].toString();
let num : number = Number(results[1]);
let node = this.root.getNodeByName(name,num);
// if root then delete everything
if (node?.getName() === 'Root') {
this.root.DeleteTheChildren();
this.RefreshFileTree();
return;
}
// remove the correct nodes
const parent = node?.getParent();
const newchildren = parent?.getChildren().filter(child => child.getName() !== node?.getName());
parent?.setChildren(newchildren!);
}
// refresh the tree visual
this.RefreshFileTree();
}
// Create the actual Image
CreateImage() {
const imagename = this.getImageName();
if (imagename === true) return;
this.root.DeleteParentRef();
this.SendTree(imagename);
}
// get the name of the image and add the time to it
getImageName() {
const imgElement = document.getElementById('ImageName') as HTMLInputElement;
const imgname = imgElement.value;
// make sure name is included
if (imgname === undefined || imgname === null || imgname === '') {
alert('Must include a name for the image');
return true;
}
// docker states all image names must be lowercase, so have to check for that
if (/[A-Z]/.test(imgname) === true) {
alert('Image name must be all lowercase');
return true;
}
// get the current time to add to the name so no images have same name
let time = new Date().toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
// get rid of the colons and add the time to the image name
time = time.replace(/:/g,'');
return imgname + time;
}
// send the full tree structure to the server side
async SendTree(imgname : string) {
const data = {
root: this.root, imgname: imgname, email: getEmail()
};
// create a form data to hold both JSON and the files
const formdata = new FormData();
// append each file to the form data with the key files
this.allFiles?.forEach((file : File) => {
formdata.append("files", file, file.name);
console.log(file.name);
});
formdata.append('data', JSON.stringify(data));
// send the post request
const res = await fetch('api/AddImage', {
method: 'POST',
body: formdata
});
if (res.ok) {
// go back to modify contest
}
}
// clear the input fields after each addition/error
ClearInputs() {
// contents of file
let contentElement = document.getElementById('filecontents') as HTMLTextAreaElement;
contentElement.value = '';
// name of file
let filenameElement = document.getElementById('filename') as HTMLInputElement;
filenameElement.value = '';
// file input box
let fileinputElement = document.getElementById('FileInput') as HTMLInputElement;
fileinputElement.value = '';
}
// go back to Modify Contest
navtoPageMC() {
gotoPage(this.router, '/modify-contest');
}
}

@ -0,0 +1,104 @@
html {
background-color: green;
}
body {
background-color: white;
}
form {
text-align: center;
}
h1, h3 {
text-align: center;
font-weight: bold;
}
#Inputs {
display: grid;
grid-template-columns: max-content max-content;
grid-gap: 5px;
margin-left: 25%;
margin-top: 25%;
}
label {
text-align: right;
}
#Submit {
justify-content: center;
align-items: center;
text-align: center;
}
#submit * {
width: 15%;
height: 5%;
}
#Submit button {
background-color: red;
font-weight: bold;
}
#hint1Text, #hint2Text, #hint3Text {
display: none;
}
/* Show the respective hint fields based on the selected radio button */
#hint1:checked ~ #hint1Text {
display: block;
}
#hint2:checked ~ #hint1Text,
#hint2:checked ~ #hint2Text {
display: block;
}
#hint3:checked ~ #hint1Text,
#hint3:checked ~ #hint2Text,
#hint3:checked ~ #hint3Text{
display: block;
}
/* for Create Image */
#body {
display: grid;
grid-template-rows: 25% auto auto auto auto auto 25%;
grid-template-columns: 20% 20% 20% 20% 20%;
justify-items: center;
}
#labels {
grid-row: 1/2;
grid-column: 3/4;
}
#Butts {
grid-row: 2/3;
grid-column: 3/4;
align-self: flex-end;
}
#TextBox {
grid-row: 3/6;
grid-column: 1/3;
}
textarea {
resize: none;
}
#DropBox {
grid-row: 3/6;
grid-column: 3/4;
border: 3px black dashed;
align-content: center;
justify-items: center;
}
#SendFile {
grid-row: 6/7;
grid-column: 3/4;
}
#FileTree {
grid-row: 3/6;
grid-column: 4/6;
}
#Submition {
grid-row: 7/8;
grid-column: 3/4;
}
.selected-node {
background-color: #f0f0f0;
font-weight: bold;
color: #333;
}

@ -0,0 +1,35 @@
<!--
Alex Miller
Jordan Latimer
Changing passwords of users for Admin
-->
<html>
<head>
<title> Change Password </title>
</head>
<body>
<!-- everything needed for adding a student -->
<h1> Change Password </h1>
<h3 id="Email">Email: {{emailinput}}</h3>
<form id="Inputs">
<label> Change Password: </label>
<input *ngIf="!showPass" id="PS1" type="password" [(ngModel)]="newPassword" name="newPassword" align="middle">
<input *ngIf="showPass" id="PS1" type="text" [(ngModel)]="newPassword" name="newPassword" align="middle">
<label> Confirm Password: </label>
<input *ngIf="!showPass" id="PS2" type="password" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
<input *ngIf="showPass" id="PS2" type="text" [(ngModel)]="confirmPassword" name="confirmPassword" align="middle">
<label> Show Password: </label>
<input type='checkbox' (click)='ShowPassword()' id='ShowPass'>
</form>
<br>
<br>
<!-- submitting or closing -->
<div id="Submit">
<input id="EditPass" type="submit" (click)="EditPass();">
<button onclick="self.close();"> Cancel </button>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditStudentComponent } from './edit-student.component';
describe('EditStudentComponent', () => {
let component: EditStudentComponent;
let fixture: ComponentFixture<EditStudentComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EditStudentComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EditStudentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,51 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgIf } from '@angular/common';
@Component({
selector: 'app-edit-student',
imports: [FormsModule, NgIf],
templateUrl: './edit-student.component.html',
styleUrl: './edit-student.component.css'
})
export class EditStudentComponent {
emailinput: string | null = sessionStorage.getItem('selectedUser');;
showPass: boolean = false;
newPassword: string = '';
confirmPassword: string = '';
// edit the password of a student
async EditPass() {
const ps1 = this.newPassword;
const ps2 = this.confirmPassword
// password protection
if (ps1.length < 10 || ps1 == "") {
alert("password must be at least 10 characters");
return;
}
if (ps1 == ps2) { // if both passwords are equal, continue
const data = { email: this.emailinput, password: ps1};
const res = await fetch('api/UpdateStudent', {
method: "POST",
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
console.log("Success: changing password");
window.close();
}
else {
console.log("ERROR: changing password");
}
}
else {
alert('Passwords do not match');
}
}
ShowPassword(){
this.showPass = !this.showPass;
}
}

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AdminGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const role = sessionStorage.getItem('role');
if (role === 'admin') return true;
this.router.navigate(['/unauthorized']);
return false;
}
}

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class UserGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const role = sessionStorage.getItem('role');
if (role === 'user') return true;
this.router.navigate(['/unauthorized']);
return false;
}
}

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const email = sessionStorage.getItem('email');
if (!email) {
this.router.navigate(['/unauthorized']);
return false;
}
return true;
}
}

@ -0,0 +1,89 @@
/*
Alex Miller
Jordan Latimer
CSS for Profile Page User Side
*/
body {
display: grid;
/* columns and rows sizes */
grid-template-columns: auto auto auto auto auto auto;
grid-template-rows: auto auto auto auto auto;
background-color: lightgreen;
}
/* Header */
#Header {
grid-row: 1/2;
grid-column: 1/7;
background-color: green;
margin: 50px;
text-align: center;
}
/* past contests table */
#PastContests {
grid-column: 1/4;
grid-row: 2/6;
margin: 50px;
height: 500px;
text-align: center;
background-color: green;
}
#Past_Contests {
margin: 10px;
height: 75%;
overflow-y: scroll;
}
/* stuff with no ID */
table {
width: 100%;
border: 3px solid black;
border-collapse: seperate;
border-spacing: 0;
background-color: white;
}
table thead {
top: 0;
position: sticky;
position: -webkit-sticky;
z-index: 1;
background-color: black;
color: white;
}
td {
border: 3px solid black;
text-align: center;
height: 50px;
}
h1 {
display: inline;
font-weight: bold;
}
/* Leaderboard popup window */
#Leaderboard {
text-align: center;
}
#Leaderboard h1 {
grid-row: 1/2;
grid-column: 2/5;
text-align: center;
}
#Leaderboard h2 {
grid-row: 2/3;
grid-column: 3/4;
text-align: center;
color: white;
}
#LeaderTable {
grid-column: 2/6;
grid-row: 4/5;
margin: 10px;
}

@ -0,0 +1,27 @@
<html>
<head>
<title>Leaderboard</title>
</head>
<body id="Leaderboard">
<div id="Header">
<h1> Leaderboard For </h1>
<h2 id="ContestName"> </h2>
</div>
<!-- table for leaderboard -->
<div id="LeaderTable">
<table>
<thead>
<tr>
<th> Rank </th>
<th> Name </th>
<th> Flags </th>
</tr>
</thead>
<tbody id="LeaderTBody">
</tbody>
</table>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LeaderboardComponent } from './leaderboard.component';
describe('LeaderboardComponent', () => {
let component: LeaderboardComponent;
let fixture: ComponentFixture<LeaderboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LeaderboardComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LeaderboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-leaderboard',
imports: [],
templateUrl: './leaderboard.component.html',
styleUrl: './leaderboard.component.css'
})
export class LeaderboardComponent {
}

@ -0,0 +1,46 @@
html, app-login html {
background-color: green;
height: 100vh;
}
app-login body {
height: 92vh;
margin: 25px;
display: grid;
background-color: white;
grid-template-rows: auto auto auto;
grid-template-columns: auto auto auto;
}
#inputFields {
grid-row: 2/3;
grid-column: 2/3;
background-color: green;
text-align: center;
height: 100%;
}
#loginform {
background-color: lightgreen;
margin: 10px;
height: 65%;
}
#loginform * {
margin-top: 50px;
}
a {
text-decoration: none;
}
h1 {
font-size: 400%;
font-weight: bold;
}
button {
width: 12%;
height: 7%;
font-weight: bold;
font-size: 100%;
background-color: lawngreen;
}
label {
font-weight: bold;
font-size: 100%;
}

@ -0,0 +1,33 @@
<!--
Alex Miller
Jordan Latimer
Login for CTF
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Login</title>
</head>
<body>
<div id="inputFields">
<h1>NMU CTF</h1>
<form id="loginform" (ngSubmit)="SendCreds()">
<br>
<label for="EM">Email:</label>
<input id="EM" type="text" [(ngModel)]="email" name="email" />
<br>
<label for="PS">Password:</label>
<input *ngIf="!showPass" id="PS" type="password" [(ngModel)]="password" name="password" />
<input *ngIf="showPass" id="PS" type="text" [(ngModel)]="password" name="password" />
<br>
<label for="ShowPass">Show Password</label>
<input type="checkbox" id="ShowPass" (change)="showPassword()" />
<br>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoginComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,48 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { NgIf } from '@angular/common';
@Component({
selector: 'app-login',
imports: [FormsModule, NgIf],
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
encapsulation: ViewEncapsulation.None
})
export class LoginComponent {
email: string = '';
password: string = '';
showPass: boolean = false;
constructor(private router: Router) {}
async SendCreds() {
const data = { email: this.email, password: this.password };
const res = await fetch('api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
});
if (res.ok) { // okay response from server
const RB = await res.json();
if (RB && RB.redirectTo) { // redirect client to new webpage
sessionStorage.setItem('email', RB.email);
sessionStorage.setItem('role', RB.redirectTo === '/admin-contest' ? 'admin' : 'user');
this.router.navigate([RB.redirectTo]);
}
else { // error handling
console.error("redirect URL issue");
}
}
else { // what user entered doesnt match up to db
alert("Invalid Credentials");
}
}
showPassword() {
this.showPass = !this.showPass;
}
}

@ -0,0 +1,6 @@
export interface Admin {
AdminID: number,
Email: string,
Password: string,
ActiveFlag: string
}

@ -0,0 +1,7 @@
export interface Contest {
ContestID: number,
Name: string,
IsActive: number,
AdminID: number,
Description: string
}

@ -0,0 +1,11 @@
export interface Flag{
FlagID: number,
Name: string,
Description: string,
ContestID: number,
Image: string,
Path: string,
Hint1: string,
Hint2: string,
Hint3: string
}

@ -0,0 +1,5 @@
export interface Image{
ImageID: number,
Name: string,
AdminID: number
}

@ -0,0 +1,7 @@
export interface Submission {
SubmissionID: number,
UserID: number,
FlagID: number,
IsCorrect: boolean,
Attempts: number
}

@ -0,0 +1,9 @@
export interface User{
UserID: number,
Name: string,
Email: string,
Password: string,
Flags: number,
ActiveFlag: string,
AdminID: number
}

@ -0,0 +1,178 @@
/*
Alex Miller
Jordan Latimer
CSS for Modifying Contests
*/
body {
background-color: lightblue;
display: grid;
grid-template-columns: auto auto;
grid-template-rows: auto auto auto;
}
/* Header stuff */
#HeaderStuff {
grid-row: 1/2;
grid-column: 1/3;
background-color: blue;
margin: 50px;
text-align: center;
}
#DoneButt {
height: 25px;
width: 150px;
background-color: lawngreen;
}
/* Contests */
#Contests {
background-color: blue;
grid-row: 2/3;
grid-column: 1/2;
margin: 50px;
height: 500px;
text-align: center;
}
.selected-contest {
background-color: lawngreen;
color: blue;
border: 2px solid lawngreen;
border-radius: 5px;
}
#ContestContent, #FIContent {
background-color: white;
margin: 10px;
text-align: center;
height: 75%;
}
#ContestList, #FIList {
height: 75%;
overflow-y: scroll;
}
#ActiveSpan {
color: lawngreen;
font-weight: bold;
}
#FISpan {
color: blue;
font-weight: bold;
}
/* Flags and Images */
#FlagsAndImages {
background-color: blue;
grid-row: 2/3;
grid-column: 2/3;
margin: 50px;
height: 500px;
text-align: center;
}
/* Terminal area */
#Terminal {
background-color: blue;
grid-row: 3/4;
grid-column: 1/3;
margin: 50px;
height: 525px;
}
#TerminalHeader {
text-align: center;
}
#TerminalHeader select, input {
margin: 10px;
width: 150px;
height: 25px;
}
#TerminalHeader input {
width: 150px;
height: 25px;
}
#Linux_Shell {
background-color: black;
height: 70%;
margin: 10px;
}
/* Buttons */
#Buttons {
background-color: blue;
margin-bottom: 5px;
}
input {
font-weight: bold;
width: 20%;
height: 10%;
font-size: 15px;
}
.AddButt {
background-color: lawngreen;
}
.DeleteButt {
background-color: red;
}
/* selected items */
.selected {
background-color: lightgray;
}
.selected span {
background-color: black;
}
.selected-flag {
background-color: lightgray;
}
/* tooltips */
.tooltip {
font-weight: bold;
color: white;
display: inline;
}
.tooltip:hover .tooltipText {
visibility: visible;
}
.tooltip .tooltipText {
visibility: hidden;
height: auto;
background-color: gray;
color: black;
text-align: center;
position: absolute;
padding: 5px;
}
/* Dialog box for deleting flags and images */
#DialogBox {
text-align: center;
top: 50%;
transform: translate(-50%, -50%);
padding: 100px;
}
#DialogBox input {
width: 50%;
background-color: red;
}
/* anything else not related to any of above */
h1 {
color: white;
font-weight: bold;
display: inline;
}
ul {
list-style-type: none;
text-align: center;
font-weight: bold;
height: 50%;
}
select, option {
background-color: white;
}
li {
font-size: 20px;
}

@ -0,0 +1,142 @@
<!--
Jordan Latimer
Alex Miller
Modify Contest screen for Admin
-->
<html>
<head>
<title>Contest Menu</title>
</head>
<body>
<!-- header stuff -->
<div id="HeaderStuff">
<h1> Modify Contests </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can change anything involving <span style="color:white;">Contests</span>
</span>
</div>
<br>
<br>
<input type="button" id="DoneButt" (click)="navtoPageAP()" value="Back to Profile">
</div>
<!-- Contests -->
<div id="Contests">
<h1> Contests </h1>
<div class="tooltip">
?
<span class="tooltipText">
select a <span style="color:white;">Contest</span> by <i>clicking</i> on one
<br>
<span style="color:lawngreen;">Add:</span> add a new contest
<br>
<span style="color:blue;">Active:</span> set a <i>selected</i> contest active and available for your students
<br>
<span style="color:red;">Delete:</span> delete a <i>selected</i> contest
</span>
</div>
<br>
<br>
<div id="ContestContent">
<!-- buttons to manipulate contests -->
<div id="Buttons">
<input class="AddButt" type="button" (click)="addContestPopup()" value="Add">
<input type="button" (click)="setContestActive()" value="Activate">
<input class="DeleteButt" type="button" (click)="deleteContest();" value="Delete">
</div>
<!-- list of contests -->
<div id="ContestList">
<ul id="ContestListItems">
<li *ngFor="let contest of contests" (click)="selectContest(contest.ContestID)"
[ngClass]="{'selected-contest': contest.ContestID === selectedContestId}">
{{ contest.Name }}
<span *ngIf="contest.ContestID === activeContest"></span>
</li>
</ul>
</div>
</div>
</div>
<!-- Flags and Images -->
<div id="FlagsAndImages">
<h1> Flags and Images </h1>
<div class="tooltip">
?
<span class="tooltipText">
when selecting a contest, the <span style="color:white;">Flags</span> and <span style="color:white;">Images</span> of that contest will show up here.
<br>
each <span style="color:white;">Flag</span> <span style="color:red;">NEEDS</span> a <i>selected</i> contest to be <span style="color:lawngreen;">Added</span>
<br>
this is not the case for <span style="color:white;">Images</span>
<br>
Some <span style="color:white;">Flags</span> may show up with <span style="color: red;">NONE</span>
but be in different <span style="color:white;">Contests</span>
you can select <span style="color:white;">Flags</span> to delete or change by <i>clicking</i> on them.
</span>
</div>
<br>
<br>
<div id="FIContent">
<!-- buttons for flags and images -->
<div id="Buttons">
<input class="AddButt" type="button" (click)="addFlagPopup()" value="Add Flag">
<input class="DeleteButt" type="button" (click)="showDialogBox()" value="Delete">
<input class="AddButt" type="button" value="Add Image" (click)="navtoPageAI()">
</div>
<!-- list of flags and images -->
<div id="FIList">
<ul id="FIListItems">
<li *ngFor="let flag of flagsForContest" (click)="selectFlag(flag)"
[ngClass]="{'selected-contest': flag.FlagID === selectedFlag?.FlagID}">
{{ flag.Name }} ({{flag.Image}})
</li>
</ul>
</div>
</div>
</div>
<!-- linux shell to test images -->
<div id="Terminal">
<div id="TerminalHeader">
<h1> Test Images </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can <i>test</i> the <span style="color:white;">Images</span> you create.
<br>
Simply select one from the dropdown and press <i>Test Image</i>
</span>
</div>
<br>
<select id="Images" [(ngModel)]="selectedImage">
<option disabled selected hidden> Select an Image </option>
<option *ngFor="let image of allImages" [value] ="image.Name">{{image.Name}}</option>
</select>
<input type="button" value="Test Image" (click)="TestImage()">
</div>
<!-- the actual shell -->
<div id="Linux_Shell">
</div>
</div>
<!-- Dialog box for deleting flag or image -->
<dialog id="DialogBox" [open]="isDialogVisible">
<h3> Which would you want to Delete: </h3>
<p id="FlagImage">{{ selectedFlag?.Image }}</p>
<input type="button" value="Delete Flag" (click)="DeleteFlag()">
<input type="button" value="Delete Image" (click)="DeleteImage()">
<input type="button" value="Cancel" (click)="closeDialogBox()">
</dialog>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ModifyContestComponent } from './modify-contest.component';
describe('ModifyContestComponent', () => {
let component: ModifyContestComponent;
let fixture: ComponentFixture<ModifyContestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ModifyContestComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ModifyContestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,210 @@
import { Component, Renderer2 } from '@angular/core';
import { Router } from '@angular/router';
import { TerminalService } from '../Helper/terminal.service';
import { getEmail, gotoPage, Popup, updateContainer } from '../Helper/Helpers';
import { ModifyContestService } from './modify-contest.service';
import { Contest } from '../models/contest.model';
import { NgFor, CommonModule } from '@angular/common';
import { Flag } from '../models/flag.model';
import { Image } from '../models/image.model';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-modify-contest',
imports: [NgFor, CommonModule, FormsModule],
templateUrl: './modify-contest.component.html',
styleUrl: './modify-contest.component.css'
})
export class ModifyContestComponent {
contests: Contest[] = [];
activeContest: number | null = null;
flagsForContest: Flag[] = [];
selectedContestId: number = 0;
selectedFlag: Flag | null = null;
allImages: Image[] = []
isDialogVisible = false;
selectedImage: string = "Select an Image";
constructor(private renderer: Renderer2, private router: Router, private modifyContestService: ModifyContestService, private terminalService: TerminalService){}
ngOnInit(): void {
this.terminalService.reattachTerminal();
// style class for blue border
this.renderer.addClass(document.documentElement, 'modify');
this.loadContests();
this.getImages();
window.addEventListener("message", this.handlePopupMessage.bind(this));
}
// set app back to default when leaving this page
ngOnDestroy(): void {
this.renderer.removeClass(document.documentElement, 'modify');
}
handlePopupMessage(event: MessageEvent): void {
if (event.origin !== window.location.origin) return;
if (event.data?.type === "FLAG_ADDED" && event.data?.contestId) {
this.modifyContestService.loadFlagsForContest(event.data.contestId)
.then(flags => this.flagsForContest = flags)
.catch(err => console.error('Error reloading flags:', err));
}
}
// Method to load contests
async loadContests(): Promise<void> {
const email = this.getEmail();
try {
const contestsData = await this.modifyContestService.loadContests(email);
this.contests = contestsData || []; // Populate contests array
this.activeContest = await this.modifyContestService.getActiveContest(this.getEmail()); //Getting the active contest
} catch (error) {
console.error('Error loading contests:', error);
}
}
async getActiveContest(): Promise<void> {
this.activeContest = await this.modifyContestService.getActiveContest(this.getEmail());
}
async getImages(): Promise<void>{
const email = this.getEmail();
try{
const imageData = await this.modifyContestService.getImages(email);
this.allImages = imageData || [];
}catch(error){
console.error('Error loading images');
}
}
// Method to add a contest
addContestPopup(): void {
Popup('/add-contest');
}
addFlagPopup(): void{
if(this.selectedContestId === 0)
alert("Select a contest to add a flag!");
else{
Popup('/add-flag');
}
}
navtoPageAI(): void{
gotoPage(this.router, '/create-image');
}
// Method to set a contest as active
async setContestActive(): Promise<void> {
if (this.selectedContestId !== null) {
try {
const email = this.getEmail();
await this.modifyContestService.setContestActive(this.selectedContestId, email);
this.loadContests();
console.log('Contest set as active');
} catch (error) {
console.error('Error activating contest:', error);
}
}
}
async deleteContest(): Promise<void> {
if (this.selectedContestId === 0)
alert("Select a contest to delete!");
else if(this.activeContest === this.selectedContestId)
alert("You can't delete an active contest!");
else{
try {
await this.modifyContestService.deleteContest(this.selectedContestId);
console.log('Contest deleted');
this.loadContests(); // Refresh the contest list after deletion
this.selectedContestId = 0;
this.flagsForContest = [];
} catch (error) {
console.error('Error deleting contest:', error);
}
}
}
selectContest(contestId: number): void {
this.selectedContestId = contestId;
this.modifyContestService.loadFlagsForContest(contestId)
.then((flags) => {
this.flagsForContest = flags;
}).catch((err) => {
console.error('Failed to load flags:', err);
})
sessionStorage.setItem('selectedContestID', this.selectedContestId.toString());
console.log('Selected contest ID:', contestId);
}
selectFlag(flag: Flag): void{
this.selectedFlag = flag
}
getEmail(): string {
return getEmail();
}
navtoPageAP() {
gotoPage(this.router, '/admin-profile');
}
showDialogBox(): void{
if(!this.selectedFlag || this.selectedFlag.Name === null){
alert('You must select a flag first');
return;
}
console.log('Opening dialog for flag:', this.selectedFlag);
this.isDialogVisible = true;
}
closeDialogBox(): void{
this.isDialogVisible = false;
}
async DeleteFlag(): Promise<void>{
if(!this.selectedFlag){
console.log("No flag selected");
alert("Please select a flag to delete.");
return;
}
try{
await this.modifyContestService.DeleteFlag(this.selectedFlag.FlagID);
this.flagsForContest = await this.modifyContestService.loadFlagsForContest(this.selectedContestId);
} catch(error){
console.error("Failed deleting flag:", error);
}
console.log("Deleted Flag");
this.isDialogVisible = false;
this.selectedFlag = null;
}
DeleteImage(): void{
this.closeDialogBox();
}
async TestImage(): Promise<void>{
if(this.selectedImage === "Select an Image"){
alert('you must select an Image first');
return;
}
try{
await this.modifyContestService.setNewActiveFlag(this.selectedImage, getEmail())
} catch (error) {
console.error("Failed setting up new image", error);
}
await updateContainer(getEmail());
this.ClearAndSetTerminal();
}
// clear and set terminal for new flag
async ClearAndSetTerminal() {
await this.terminalService.ClearTerminal();
await this.terminalService.reattachTerminal();
}
}

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ModifyContestService } from './modify-contest.service';
describe('ModifyContestService', () => {
let service: ModifyContestService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ModifyContestService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,232 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ModifyContestService {
constructor() {}
async getImages(email: string): Promise<any> {
try {
const response = await fetch('api/getImages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error('Failed to fetch images');
}
return await response.json();
} catch (error) {
console.error('Error fetching images:', error);
throw error;
}
}
async loadContests(email: string): Promise<any> {
try {
const response = await fetch('api/contests/getContests', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error('Failed to fetch contests');
}
return await response.json();
} catch (error) {
console.error('Error fetching contests:', error);
throw error;
}
}
async loadFlagsForContest(contestId: number): Promise<any> {
if(contestId === 0)
return [];
try {
const response = await fetch('api/flags/getAllFlagsFromContest', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ contest: contestId })
});
if (!response.ok) {
throw new Error('Failed to fetch flags');
}
return await response.json();
} catch (error) {
console.error('Error fetching flags:', error);
throw error;
}
}
async addContest(data: any): Promise<any> {
try {
const response = await fetch('api/AddContest', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('Failed to add contest');
}
return await response.json();
} catch (error) {
console.error('Error adding contest:', error);
throw error;
}
}
async deleteContest(contestId: number): Promise<any> {
// Send a POST request to delete the contest by its name
let data = {contest: contestId};
await this.deleteFlagsFromContest(contestId);
let res = await fetch('api/DeleteContest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (res.ok) {
console.log("Success: deleting contest");
} else {
console.log("ERROR: deleting contest");
alert("Failed to delete contest.");
}
}
async deleteFlagsFromContest(contestId: number): Promise<any> {
try {
const response = await fetch('api/DeleteFlagsFromContest', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ contest: contestId })
});
if (!response.ok) {
throw new Error('Failed to delete flags');
}
return await response.json();
} catch (error) {
console.error('Error deleting flags:', error);
throw error;
}
}
async AddFlag(data: any): Promise<any> {
try {
const response = await fetch('api/AddFlag', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('Failed to add flag');
}
return await response.json();
} catch (error) {
console.error('Error adding flag:', error);
throw error;
}
}
async DeleteFlag(flagId: number): Promise<any> {
let data = { flag: flagId };
let res = await fetch('api/DeleteFlag', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
}
async setNewActiveFlag(flagImage: string, Email: string):Promise<any> {
const image = flagImage;
// set up the container for the image
const data = { FlagImage: image, email: Email };
const res = await fetch('/api/setNewActiveFlag', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
}
async setContestActive(contestId: number, email: string): Promise<any> {
try {
const response = await fetch('api/setContestActive', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ contest: contestId, email })
});
if (!response.ok) {
throw new Error('Failed to set contest active');
}
return await response.json();
} catch (error) {
console.error('Error setting contest active:', error);
throw error;
}
}
async deleteImage(imageName: string): Promise<any> {
try {
const response = await fetch('api/DeleteImageReplaceFlags', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ images: imageName })
});
if (!response.ok) {
throw new Error('Failed to delete image');
}
return await response.json();
} catch (error) {
console.error('Error deleting image:', error);
throw error;
}
}
async getActiveContest(Email: string): Promise<any>{
const data = { email: Email };
const res = await fetch('api/contests/getActiveContest', {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(data)
});
if (res.ok) {
let contest = await res.json();
return contest.ContestID;
}
else
return 0;
}
}

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SocketIOService } from './socket-io.service';
describe('SocketIOService', () => {
let service: SocketIOService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SocketIOService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,48 @@
import { Injectable } from '@angular/core';
import { io, Socket } from 'socket.io-client';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SocketIOService {
private socket: Socket;
constructor() {
this.socket = io("/api/socket.io", {
path: '/api/socket.io',
transports: ['websocket'], // or ['polling', 'websocket'] if you want fallback
autoConnect: false,
});
}
connect(): void {
if (!this.socket.connected) {
console.log("Attempting to connect...");
this.socket.connect();
this.socket.on("connect", () => console.log("Connected to Socket.IO!"));
this.socket.on("connect_error", (err) => console.error("Socket.IO Connection Error:", err));
}
}
disconnect(): void {
if (this.socket.connected) {
this.socket.disconnect();
console.log("Disconnected from Socket.IO.");
}
}
listen<T = any>(eventName: string): Observable<T> {
return new Observable<T>((subscriber) => {
const handler = (data: T) => subscriber.next(data);
this.socket.on(eventName, handler);
return () => this.socket.off(eventName, handler); // cleanup
});
}
emit(eventName: string, data: any): void {
this.socket.emit(eventName, data);
}
}

@ -0,0 +1,3 @@
<h1>Thought you could pull a fast one eh?</h1>
<p>I dont think so</p>
<h2>👎</h2>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UNAUTHORIZEDComponent } from './unauthorized.component';
describe('UNAUTHORIZEDComponent', () => {
let component: UNAUTHORIZEDComponent;
let fixture: ComponentFixture<UNAUTHORIZEDComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UNAUTHORIZEDComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UNAUTHORIZEDComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-unauthorized',
imports: [],
templateUrl: './unauthorized.component.html',
styleUrl: './unauthorized.component.css'
})
export class UNAUTHORIZEDComponent {
}

@ -0,0 +1,160 @@
.body {
height: 98vh;
display: grid;
/* columns and rows sizes */
grid-template-columns: auto auto auto auto auto auto;
grid-template-rows: auto auto auto auto auto;
background-color: lightgreen;
}
html {
background-color: green;
}
.bg {
background-color: green;
}
/* Header */
#Header {
grid-row: 1/2;
grid-column: 1/7;
background-color: green;
margin: 50px;
text-align: center;
}
#Header input {
height: 25px;
width: 150px;
font-weight: bold;
}
#ContestsButt {
background-color: lawngreen;
}
#LogoutButt {
background-color: red;
}
/* past contests table */
#PastContests {
grid-column: 1/4;
grid-row: 2/6;
margin: 50px;
height: 500px;
text-align: center;
background-color: green;
}
#Past_Contests {
margin: 10px;
height: 75%;
overflow-y: scroll;
}
/* record table */
#Record, #CurrentContest {
grid-column: 5/7;
grid-row: 2/3;
margin: 50px;
text-align: center;
background-color: green;
}
#RecordTable, #Current_Contest {
margin: 10px;
}
/* Usernames */
#Username {
grid-row: 3/5;
grid-column: 5/7;
background-color: green;
text-align: center;
margin: 50px;
}
#generate {
background-color: blue;
color: white;
}
#setname {
background-color: lawngreen;
}
#generate, #setname {
height: 25px;
width: 150px;
font-weight: bold;
}
/* tooltips */
.tooltip {
font-weight: bold;
color: white;
display: inline;
}
.tooltip:hover .tooltipText {
visibility: visible;
}
.tooltip .tooltipText {
visibility: hidden;
height: auto;
background-color: gray;
color: black;
text-align: center;
position: absolute;
padding: 5px;
z-index: 100;
}
/* stuff with no ID */
table {
width: 100%;
border: 3px solid black;
border-collapse: seperate;
border-spacing: 0;
background-color: white;
}
table thead {
top: 0;
position: sticky;
position: -webkit-sticky;
z-index: 1;
background-color: black;
color: white;
}
td {
border: 3px solid black;
text-align: center;
height: 50px;
}
button {
width: 50%;
height: 50%;
background-color: lawngreen;
font-weight: bold;
}
h1 {
display: inline;
font-weight: bold;
}
/* Leaderboard popup window */
#Leaderboard {
text-align: center;
}
#Leaderboard h1 {
grid-row: 1/2;
grid-column: 2/5;
text-align: center;
}
#Leaderboard h2 {
grid-row: 2/3;
grid-column: 3/4;
text-align: center;
color: white;
}
#LeaderTable {
grid-column: 2/6;
grid-row: 4/5;
margin: 10px;
}

@ -0,0 +1,75 @@
<div class="bg">
<div class="body">
<!-- header -->
<div id="Header">
<h1> Menu </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is the main place for you, the <span style="color:white">User</span>
</span>
</div>
<br>
<br>
<input id="ContestsButt" type="button" value="Profile" (click)="navtoPage()">
</div>
<!-- past contests -->
<div id="PastContests">
<h1> Past Contests </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is every past contest that's still around
<br>
you can view the leaderboard or hop in to take a look
</span>
</div>
<br>
<br>
<!-- past contest table -->
<div id="Past_Contests">
<table>
<thead>
<tr>
<th> Contest Name </th>
<th> Flags </th>
<th> Leaderboard </th>
</tr>
</thead>
<tbody id="UPCTbody">
</tbody>
</table>
</div>
</div>
<!-- current contest-->
<div id="CurrentContest">
<h1> Current Contest </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where the current contest is shown
<br>
click <span style="color:lawngreen">Join</span> to join in on the fun
</span>
</div>
<br>
<br>
<!-- Current Contest -->
<div id="Current_Contest">
<table>
<thead>
<th> Current Contest </th>
<th> Flags </th>
<th> Join </th>
</thead>
<tbody id="UCCTbody">
</tbody>
</table>
</div>
</div>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserMenuComponent } from './user-menu.component';
describe('UserMenuComponent', () => {
let component: UserMenuComponent;
let fixture: ComponentFixture<UserMenuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserMenuComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UserMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,24 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router'
import { Contest } from '../models/contest.model.js';
import { PopulateTable, InsertIntoTable, gotoPage, getContests } from '../Helper/Helpers.js';
@Component({
selector: 'app-user-menu',
imports: [],
templateUrl: './user-menu.component.html',
encapsulation: ViewEncapsulation.None,
styleUrl: './user-menu.component.css'
})
export class UserMenuComponent implements OnInit{
constructor(private router: Router){}
async ngOnInit(): Promise<void> {
let data: Contest[] = await getContests();
InsertIntoTable(data, this.router);
}
navtoPage(): void {
gotoPage(this.router, '/user-profile');
}
}

@ -0,0 +1,138 @@
/*
Alex Miller
Jordan Latimer
CSS for Profile Page User Side
*/
body {
height: 98vh;
display: grid;
/* columns and rows sizes */
grid-template-columns: auto auto auto auto auto auto;
grid-template-rows: auto auto auto auto auto;
background-color: lightgreen;
}
html {
background-color: green;
}
/* Header */
#Header {
grid-row: 1/2;
grid-column: 1/7;
background-color: green;
margin: 50px;
text-align: center;
}
#Header input {
height: 25px;
width: 150px;
font-weight: bold;
}
#ContestsButt {
background-color: lawngreen;
}
#LogoutButt {
background-color: red;
}
/* past contests table */
#PastContests {
grid-column: 1/4;
grid-row: 2/6;
margin: 50px;
height: 500px;
text-align: center;
background-color: green;
}
#Past_Contests {
margin: 10px;
height: 75%;
overflow-y: scroll;
}
/* record table */
#Record, #CurrentContest {
grid-column: 5/7;
grid-row: 2/3;
margin: 50px;
text-align: center;
background-color: green;
}
#RecordTable, #Current_Contest {
margin: 10px;
}
/* Usernames */
#Username {
grid-row: 3/5;
grid-column: 5/7;
background-color: green;
text-align: center;
margin: 50px;
}
#generate {
background-color: blue;
color: white;
}
#setname {
background-color: lawngreen;
}
#generate, #setname {
height: 25px;
width: 150px;
font-weight: bold;
}
/* tooltips */
.tooltip {
font-weight: bold;
color: white;
display: inline;
}
.tooltip:hover .tooltipText {
visibility: visible;
}
.tooltip .tooltipText {
visibility: hidden;
height: auto;
background-color: gray;
color: black;
text-align: center;
position: absolute;
padding: 5px;
z-index: 100;
}
/* stuff with no ID */
h1 {
display: inline;
font-weight: bold;
}
/* Leaderboard popup window */
#Leaderboard {
text-align: center;
}
#Leaderboard h1 {
grid-row: 1/2;
grid-column: 2/5;
text-align: center;
}
#Leaderboard h2 {
grid-row: 2/3;
grid-column: 3/4;
text-align: center;
color: white;
}
#LeaderTable {
grid-column: 2/6;
grid-row: 4/5;
margin: 10px;
}

@ -0,0 +1,124 @@
<!--
Alex Miller
Jordan Latimer
Profile Page for Users
-->
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NMU CTF Profile Page</title>
</head>
<body>
<!-- Header -->
<div id="Header">
<h1> Profile </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is everything about your profile
<br>
you can view your Past Contests, Record,
<br>
and change your username
</span>
</div>
<br>
<br>
<input id="ContestsButt" type="button" value="Back to Contests" (click)="navtoPage()">
<input id="LogoutButt" type="button" value="Logout" (click)="onLogout()">
</div>
<!-- Past contest table -->
<div id="PastContests">
<h1> Past Contests </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is every past contest that's still around
<br>
you can see the <span style="color:white">Leaderboard</span> by clicking
<br>
on <span style="color:lawngreen">View</span>
</span>
</div>
<br>
<br>
<!-- past contest table -->
<div id="Past_Contests">
<table>
<thead>
<tr>
<th> Contest Name </th>
<th> Flags </th>
<th> Leaderboard </th>
</tr>
</thead>
<tbody id="UPCTBody">
</tbody>
</table>
</div>
</div>
<!-- chaning username -->
<div id="Username">
<h1> Edit Username</h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can change your username
<br>
simply click <span style="color:blue">Generate</span> and a random
<br>
username will be generated, keep going until you find one you like and
<br>
click <span style="color:lawngreen">Set Name</span> to set that as your username
</span>
</div>
<br>
<br>
<!-- Name generator and logout button -->
<div id="Name">
<input type="text" id="NameInput" [(ngModel)]="NameInput" readonly>
<input id="generate" type="button" value="Generate" (click)="onGetNewName()">
<br>
<br>
<br>
<input id="setname" type="button" value="Set Name" (click)="onSetNewName()">
</div>
</div>
<!-- users record -->
<div id="Record">
<h1> Record </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can view your <span style="color:white">Record</span>
<br>
to see how well you are doing with all <span style="color:white">Contests</span>
</span>
</div>
<br>
<br>
<!-- record table -->
<div id="RecordTable">
<table>
<thead>
<tr>
<th> Completed </th>
<th> Total </th>
<th> Record </th>
</tr>
</thead>
<tbody id="URTbody">
</tbody>
</table>
</div>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserProfileACComponent } from './user-profile-ac.component';
describe('UserProfileACComponent', () => {
let component: UserProfileACComponent;
let fixture: ComponentFixture<UserProfileACComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserProfileACComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UserProfileACComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,37 @@
import { Component } from '@angular/core';
import { loadTable, getNewName, setNewName } from '../Helper/User-pfp';
import { Router } from '@angular/router';
import { gotoPage, logOut } from '../Helper/Helpers';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-user-profile-ac',
imports: [FormsModule],
templateUrl: './user-profile-ac.component.html',
styleUrl: './user-profile-ac.component.css'
})
export class UserProfileACComponent {
constructor(private router: Router){}
NameInput: string = '';
async ngOnInit(): Promise<void> {
this.NameInput = await loadTable();
}
navtoPage(): void {
gotoPage(this.router, '/contest-page');
}
onGetNewName(): void{
this.NameInput = getNewName();
}
onSetNewName(): void{
setNewName(this.NameInput);
}
onLogout(): void{
logOut(this.router);
}
}

@ -0,0 +1,138 @@
/*
Alex Miller
Jordan Latimer
CSS for Profile Page User Side
*/
body {
height: 98vh;
display: grid;
/* columns and rows sizes */
grid-template-columns: auto auto auto auto auto auto;
grid-template-rows: auto auto auto auto auto;
background-color: lightgreen;
}
html {
background-color: green;
}
/* Header */
#Header {
grid-row: 1/2;
grid-column: 1/7;
background-color: green;
margin: 50px;
text-align: center;
}
#Header input {
height: 25px;
width: 150px;
font-weight: bold;
}
#ContestsButt {
background-color: lawngreen;
}
#LogoutButt {
background-color: red;
}
/* past contests table */
#PastContests {
grid-column: 1/4;
grid-row: 2/6;
margin: 50px;
height: 500px;
text-align: center;
background-color: green;
}
#Past_Contests {
margin: 10px;
height: 75%;
overflow-y: scroll;
}
/* record table */
#Record, #CurrentContest {
grid-column: 5/7;
grid-row: 2/3;
margin: 50px;
text-align: center;
background-color: green;
}
#RecordTable, #Current_Contest {
margin: 10px;
}
/* Usernames */
#Username {
grid-row: 3/5;
grid-column: 5/7;
background-color: green;
text-align: center;
margin: 50px;
}
#generate {
background-color: blue;
color: white;
}
#setname {
background-color: lawngreen;
}
#generate, #setname {
height: 25px;
width: 150px;
font-weight: bold;
}
/* tooltips */
.tooltip {
font-weight: bold;
color: white;
display: inline;
}
.tooltip:hover .tooltipText {
visibility: visible;
}
.tooltip .tooltipText {
visibility: hidden;
height: auto;
background-color: gray;
color: black;
text-align: center;
position: absolute;
padding: 5px;
z-index: 100;
}
/* stuff with no ID */
h1 {
display: inline;
font-weight: bold;
}
/* Leaderboard popup window */
#Leaderboard {
text-align: center;
}
#Leaderboard h1 {
grid-row: 1/2;
grid-column: 2/5;
text-align: center;
}
#Leaderboard h2 {
grid-row: 2/3;
grid-column: 3/4;
text-align: center;
color: white;
}
#LeaderTable {
grid-column: 2/6;
grid-row: 4/5;
margin: 10px;
}

@ -0,0 +1,125 @@
<!--
Alex Miller
Jordan Latimer
Profile Page before entering a contest for User
-->
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NMU CTF User Profile Page</title>
</head>
<body>
<!-- Header -->
<div id="Header">
<h1> Profile </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is everything about your profile
<br>
you can view your Past Contests, Record,
<br>
and change your username
</span>
</div>
<br>
<br>
<input id="ContestsButt" type="button" value="Back to Menu" (click)="navtoPage()">
<input id="LogoutButt" type="button" value="Logout" (click)="onLogout()">
</div>
<!-- past contests -->
<div id="PastContests">
<h1> Past Contests </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is every past contest that's still around
<br>
you can see the <span style="color:white">Leaderboard</span> by clicking
<br>
on <span style="color:lawngreen">View</span>
</span>
</div>
<br>
<br>
<!-- past contest table -->
<div id="Past_Contests">
<table>
<thead>
<tr>
<th> Contest Name </th>
<th> Flags </th>
<th> Leaderboard </th>
</tr>
</thead>
<tbody id="UPCTBody">
</tbody>
</table>
</div>
</div>
<!-- usernames -->
<div id="Username">
<h1> Edit Username </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can change your username
<br>
simply click <span style="color:blue">Generate</span> and a random
<br>
username will be generated, keep going until you find one you like and
<br>
click <span style="color:lawngreen">Set Name</span> to set that as your username
</span>
</div>
<br>
<br>
<!-- Name generator and logout button -->
<div id="Name">
<input type="text" id="NameInput" [(ngModel)]="NameInput" readonly>
<input id="generate" type="button" value="Generate" (click)="onGetNewName()">
<br>
<br>
<br>
<input id="setname" type="button" value="Set Name" (click)="onSetNewName()">
</div>
</div>
<!-- record -->
<div id="Record">
<h1> Record </h1>
<div class="tooltip">
?
<span class="tooltipText">
This is where you can view your <span style="color:white">Record</span>
<br>
to see how well you are doing with all <span style="color:white">Contests</span>
</span>
</div>
<br>
<br>
<!-- record table -->
<div id="RecordTable">
<table>
<thead>
<tr>
<th> Completed </th>
<th> Total </th>
<th> Record </th>
</tr>
</thead>
<tbody id="URTbody">
</tbody>
</table>
</div>
</div>
</body>
</html>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserProfileComponent } from './user-profile.component';
describe('UserProfileComponent', () => {
let component: UserProfileComponent;
let fixture: ComponentFixture<UserProfileComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserProfileComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UserProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { loadTable } from '../Helper/User-pfp';
import { gotoPage, logOut } from '../Helper/Helpers';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { getNewName, setNewName } from '../Helper/User-pfp';
@Component({
selector: 'app-user-profile',
imports: [FormsModule],
templateUrl: './user-profile.component.html',
styleUrl: './user-profile.component.css'
})
export class UserProfileComponent implements OnInit {
constructor(private router: Router){}
NameInput: string = '';
async ngOnInit(): Promise<void> {
this.NameInput = await loadTable();
}
navtoPage(): void {
gotoPage(this.router, '/user-menu');
}
onGetNewName(): void{
this.NameInput = getNewName();
}
onSetNewName(): void{
setNewName(this.NameInput);
}
onLogout(): void{
logOut(this.router);
}
}

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en" class="default">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CTFFrontend</title>
<base href="/" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws://localhost:3000;"
/>
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save