Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ module.exports = {
maxBOF: 0
}],
'padding-line-between-statements': ['error',
{blankLine: 'always', prev: 'block-like', next: 'export'}
]
{ blankLine: 'always', prev: 'block-like', next: 'export' }
],
'no-void': ['error', {
allowAsStatement: true
}]
}
}
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"license": "MIT",
"private": true,
"scripts": {
"lint": "tsc --noEmit && eslint --ext .js,.ts .",
"lint": "sh -c \"tsc --noEmit && eslint .\"",
"lint:strict": "sh -c \"tsc --noEmit -p tsconfig.strict.json ; eslint .\"",
"start": "node --enable-source-maps dist/server/index.js",
"migrate": "sh -c \"yarn build:ts && RCTF_DATABASE_MIGRATE=only yarn start\"",
"build:client": "preact build --src client/src --template client/index.html --dest dist/build --no-prerender --no-inline-css",
Expand Down Expand Up @@ -58,8 +59,8 @@
"devDependencies": {
"@types/content-disposition": "0.5.3",
"@types/nodemailer": "6.4.0",
"@typescript-eslint/eslint-plugin": "2.34.0",
"@typescript-eslint/parser": "2.34.0",
"@typescript-eslint/eslint-plugin": "3.5.0",
"@typescript-eslint/parser": "3.5.0",
"ava": "3.9.0",
"babel-eslint": "10.1.0",
"babel-plugin-transform-export-extensions": "6.22.0",
Expand All @@ -69,7 +70,7 @@
"cpy-cli": "3.1.1",
"cross-env": "7.0.2",
"cz-conventional-changelog": "3.2.0",
"eslint": "6.8.0",
"eslint": "7.3.1",
"eslint-config-preact": "1.1.1",
"eslint-config-standard": "14.1.1",
"eslint-plugin-ava": "10.3.1",
Expand Down Expand Up @@ -100,7 +101,7 @@
"snarkdown": "1.2.2",
"supertest": "4.0.2",
"svg-sprite-loader": "5.0.0",
"typescript": "3.9.5"
"typescript": "3.9.6"
},
"description": "rctf is RedpwnCTF's CTF platform. It is developed and maintained by the [redpwn](https://redpwn.net) CTF team.",
"repository": {
Expand Down
12 changes: 10 additions & 2 deletions server/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const path = require('path')

module.exports = {
env: {
node: true
Expand All @@ -7,14 +9,20 @@ module.exports = {
rules: {
},
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: path.dirname(__dirname),
project: ['./tsconfig.json']
},
overrides: [{
files: ['*.ts'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended'
],
plugins: [
'@typescript-eslint'
]
}, {
files: ['.eslintrc.js'],
parser: 'espree'
}]
}
4 changes: 4 additions & 0 deletions server/challenges/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ export interface Provider extends EventEmitter {
deleteChallenge(id: string): void;
cleanup (): void;
}

export interface ProviderConstructor {
new (options: unknown): Provider
}
10 changes: 5 additions & 5 deletions server/challenges/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import config from '../../config/server'
import path from 'path'
import { Challenge, CleanedChallenge } from './types'
import { Provider } from './Provider'
import { Provider, ProviderConstructor } from './Provider'
import { challUpdateEmitter, publishChallUpdate } from '../cache/challs'

let provider: Provider

let challenges: Challenge[] = []
let cleanedChallenges: CleanedChallenge[] = []

let challengesMap: Map<string, Challenge> = new Map()
let cleanedChallengesMap: Map<string, CleanedChallenge> = new Map()
let challengesMap = new Map<string, Challenge>()
let cleanedChallengesMap = new Map<string, CleanedChallenge>()

const cleanChallenge = (chall: Challenge): CleanedChallenge => {
const { files, description, author, points, id, name, category, sortWeight } = chall
Expand All @@ -34,8 +34,8 @@ const onUpdate = (newChallenges: Challenge[]): void => {
cleanedChallengesMap = new Map(cleanedChallenges.map(c => [c.id, c]))
}

import(path.join('../providers', config.challengeProvider.name))
.then(({ default: Provider }) => {
void import(path.join('../providers', config.challengeProvider.name))
.then(({ default: Provider }: { default: ProviderConstructor }) => {
provider = new Provider(config.challengeProvider.options)

provider.on('update', onUpdate)
Expand Down
34 changes: 17 additions & 17 deletions server/providers/challenges/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as db from '../../../database'
import { deepCopy } from '../../../util'

interface DatabaseProviderOptions {
updateInterval: number;
updateInterval?: number;
}

interface DatabaseChallenge {
Expand All @@ -16,25 +16,25 @@ interface DatabaseChallenge {
}

class DatabaseProvider extends EventEmitter implements Provider {
private _updateInterval: number
private _interval: NodeJS.Timeout
private challenges: Challenge[]
private updateInterval: number
private interval: NodeJS.Timeout
private challenges: Challenge[] = []

constructor (options: DatabaseProviderOptions) {
constructor (_options: DatabaseProviderOptions) {
super()
options = {
const options: Required<DatabaseProviderOptions> = {
updateInterval: 60 * 1000,
...options
..._options
}

this._updateInterval = options.updateInterval
this._interval = setInterval(() => this._update(), this._updateInterval)
this._update()
this.updateInterval = options.updateInterval
this.interval = setInterval(() => { void this.update() }, this.updateInterval)
void this.update()
}

async _update (): Promise<void> {
private async update (): Promise<void> {
try {
const dbchallenges: DatabaseChallenge[] = await db.challenges.getAllChallenges()
const dbchallenges = await db.challenges.getAllChallenges() as DatabaseChallenge[]

this.challenges = dbchallenges.map(({ id, data }) => {
return {
Expand All @@ -51,7 +51,7 @@ class DatabaseProvider extends EventEmitter implements Provider {
}

forceUpdate (): void {
this._update()
void this.update()
}

challengeToRow (chall: Challenge): DatabaseChallenge {
Expand All @@ -69,7 +69,7 @@ class DatabaseProvider extends EventEmitter implements Provider {
async updateChallenge (chall: Challenge): Promise<void> {
const originalData = await db.challenges.getChallengeById({
id: chall.id
})
}) as DatabaseChallenge

// If we're inserting, have sane defaults
if (originalData === undefined) {
Expand All @@ -85,17 +85,17 @@ class DatabaseProvider extends EventEmitter implements Provider {

await db.challenges.upsertChallenge(data)

this._update()
void this.update()
}

async deleteChallenge (id: string): Promise<void> {
await db.challenges.removeChallengeById({ id: id })

this._update()
void this.update()
}

cleanup (): void {
clearInterval(this._interval)
clearInterval(this.interval)
}
}

Expand Down
44 changes: 24 additions & 20 deletions server/providers/challenges/rdeploy-blob/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,29 @@ interface RDeployChallenge {
}

class RDeployBlobProvider extends EventEmitter implements Provider {
private _updateInterval: number
private _rDeployDirectory: string
private _interval: NodeJS.Timeout
private challenges: Challenge[]
private updateInterval: number
private rDeployDirectory: string
private interval: NodeJS.Timeout
private challenges: Challenge[] = []

private ready = false
private downloadMap: Map<string, string>

constructor (options: RDeployBlobProviderOptions) {
constructor (_options: RDeployBlobProviderOptions) {
super()
options = {
const options: Required<RDeployBlobProviderOptions> = {
updateInterval: 60 * 1000,
...options
..._options
}

this._updateInterval = options.updateInterval
this._rDeployDirectory = path.join(__dirname, '../../../../../', options.rDeployDirectory)
this._interval = setInterval(() => this._update(), this._updateInterval)
this.updateInterval = options.updateInterval
this.rDeployDirectory = path.join(__dirname, '../../../../../', options.rDeployDirectory)
this.interval = setInterval(() => this.update(), this.updateInterval)

this.downloadMap = new Map()
this.downloadMap = new Map<string, string>()

const fileDir = path.join(this._rDeployDirectory, options.rDeployFiles)
fs.readdir(fileDir)
const fileDir = path.join(this.rDeployDirectory, options.rDeployFiles)
void fs.readdir(fileDir)
.then(async files => {
await Promise.all(files.map(async file => {
const filePath = path.join(fileDir, file)
Expand All @@ -68,25 +68,29 @@ class RDeployBlobProvider extends EventEmitter implements Provider {

// When done uploading files, allow updates
this.ready = true
this._update()
this.update()
})
}

_update (): void {
private update (): void {
// Prevent updates if downloads not initialized
if (!this.ready) return

fs.readFile(path.join(this._rDeployDirectory, 'config.json'), 'utf8')
fs.readFile(path.join(this.rDeployDirectory, 'config.json'), 'utf8')
.then((data: string) => {
try {
const rawChallenges: RDeployChallenge[] = JSON.parse(data)
const rawChallenges = JSON.parse(data) as RDeployChallenge[]

this.challenges = rawChallenges.map((chall: RDeployChallenge): Challenge => {
const downloadUrls: File[] = chall.files.map(file => {
const basename = path.basename(file)
const fileUrl = this.downloadMap.get(basename)
if (fileUrl === undefined) {
throw new Error(`File not found: ${basename}`)
}
return {
name: normalize.normalizeDownload(basename),
url: this.downloadMap.get(basename)
url: fileUrl
}
})

Expand All @@ -109,7 +113,7 @@ class RDeployBlobProvider extends EventEmitter implements Provider {
}

forceUpdate (): void {
this._update()
this.update()
}

updateChallenge (chall: Challenge): void {
Expand All @@ -135,7 +139,7 @@ class RDeployBlobProvider extends EventEmitter implements Provider {
}

cleanup (): void {
clearInterval(this._interval)
clearInterval(this.interval)
}
}

Expand Down
3 changes: 2 additions & 1 deletion server/providers/emails/ses/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ interface SesProviderOptions {
}

export default class SesProvider implements Provider {
private sesSend: Function
private sesSend: (params: AWS.SES.Types.SendEmailRequest) => Promise<AWS.SES.Types.SendEmailResponse>

constructor (options: SesProviderOptions) {
const credentials = new AWS.Credentials({
accessKeyId: options.awsKeyId || process.env.RCTF_SES_KEY_ID,
Expand Down
2 changes: 1 addition & 1 deletion server/providers/uploads/gcs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import crypto from 'crypto'
import { Provider } from '../../../uploads/types'

interface GcsProviderOptions {
credentials: object;
credentials: Record<string, unknown>;
bucketName: string;
}

Expand Down
6 changes: 5 additions & 1 deletion server/uploads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { Provider } from './types'
import fastify from 'fastify'
import { Server, IncomingMessage, ServerResponse } from 'http'

let provider: Provider = null
let provider: Provider | null = null

export const init = (app: fastify.FastifyInstance<Server, IncomingMessage, ServerResponse> | null): void => {
const name = app === null ? 'uploads/dummy' : config.uploadProvider.name

// FIXME: use async loading
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ProviderClass = require(path.join('../providers', name)).default

provider = new ProviderClass(config.uploadProvider.options, app)
}

Expand Down
8 changes: 7 additions & 1 deletion server/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ export * as normalize from './normalize'
export * as validate from './validate'
export * as scores from './scores'

// This function does not work for non JSON stringifiable objects
/**
* Perform a deep-copy of a JSON-stringifiable object
*
* @template T
* @param {T} data data to copy
* @returns {T} deep copy of data
*/
export const deepCopy = data => {
return JSON.parse(JSON.stringify(data))
}
Expand Down
8 changes: 4 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"moduleResolution": "node",
"noImplicitAny": true,
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"inlineSourceMap": true,
"inlineSources": true,
"allowJs": true,
"outDir": "dist",
"baseUrl": "."
},
Expand Down
8 changes: 8 additions & 0 deletions tsconfig.strict.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"strict": true,
"noImplicitAny": false,
"checkJs": true,
}
}
Loading