mirror of
https://github.com/KeygraphHQ/juice-shop.git
synced 2026-11-07 11:06:06 -10:00
118 lines
4.7 KiB
TypeScript
118 lines
4.7 KiB
TypeScript
import { Op } from 'sequelize'
|
|
import { type ChallengeKey, ChallengeModel } from '../models/challenge'
|
|
import logger from './logger'
|
|
import config from 'config'
|
|
import sanitizeHtml from 'sanitize-html'
|
|
import colors from 'colors/safe'
|
|
import * as utils from './utils'
|
|
import { calculateCheatScore, calculateFindItCheatScore, calculateFixItCheatScore } from './antiCheat'
|
|
import * as webhook from './webhook'
|
|
import * as accuracy from './accuracy'
|
|
import { type Server } from 'socket.io'
|
|
import { AllHtmlEntities as Entities } from 'html-entities'
|
|
import { challenges, notifications } from '../data/datacache'
|
|
|
|
const entities = new Entities()
|
|
|
|
const globalWithSocketIO = global as typeof globalThis & {
|
|
io: SocketIOClientStatic & Server
|
|
}
|
|
|
|
export const solveIf = function (challenge: any, criteria: () => any, isRestore: boolean = false) {
|
|
if (notSolved(challenge) && criteria()) {
|
|
solve(challenge, isRestore)
|
|
}
|
|
}
|
|
|
|
export const solve = function (challenge: any, isRestore = false) {
|
|
challenge.solved = true
|
|
challenge.save().then((solvedChallenge: { difficulty: number, key: string, name: string }) => {
|
|
logger.info(`${isRestore ? colors.grey('Restored') : colors.green('Solved')} ${solvedChallenge.difficulty}-star ${colors.cyan(solvedChallenge.key)} (${solvedChallenge.name})`)
|
|
sendNotification(solvedChallenge, isRestore)
|
|
if (!isRestore) {
|
|
const cheatScore = calculateCheatScore(challenge)
|
|
if (process.env.SOLUTIONS_WEBHOOK) {
|
|
webhook.notify(solvedChallenge, cheatScore).catch((error: unknown) => {
|
|
logger.error('Webhook notification failed: ' + colors.red(utils.getErrorMessage(error)))
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
export const sendNotification = function (challenge: { difficulty?: number, key: any, name: any, description?: any }, isRestore: boolean) {
|
|
if (!notSolved(challenge)) {
|
|
const flag = utils.ctfFlag(challenge.name)
|
|
const notification = {
|
|
key: challenge.key,
|
|
name: challenge.name,
|
|
challenge: challenge.name + ' (' + entities.decode(sanitizeHtml(challenge.description, { allowedTags: [], allowedAttributes: {} })) + ')',
|
|
flag,
|
|
hidden: !config.get('challenges.showSolvedNotifications'),
|
|
isRestore
|
|
}
|
|
const wasPreviouslyShown = notifications.some(({ key }) => key === challenge.key)
|
|
notifications.push(notification)
|
|
|
|
if (globalWithSocketIO.io && (isRestore || !wasPreviouslyShown)) {
|
|
globalWithSocketIO.io.emit('challenge solved', notification)
|
|
}
|
|
}
|
|
}
|
|
|
|
export const sendCodingChallengeNotification = function (challenge: { key: string, codingChallengeStatus: 0 | 1 | 2 }) {
|
|
if (challenge.codingChallengeStatus > 0) {
|
|
const notification = {
|
|
key: challenge.key,
|
|
codingChallengeStatus: challenge.codingChallengeStatus
|
|
}
|
|
if (globalWithSocketIO.io) {
|
|
globalWithSocketIO.io.emit('code challenge solved', notification)
|
|
}
|
|
}
|
|
}
|
|
|
|
export const notSolved = (challenge: any) => challenge && !challenge.solved
|
|
|
|
export const findChallengeByName = (challengeName: string) => {
|
|
for (const challenge of Object.values(challenges)) {
|
|
if (challenge.name === challengeName) {
|
|
return challenge
|
|
}
|
|
}
|
|
logger.warn('Missing challenge with name: ' + challengeName)
|
|
}
|
|
|
|
export const findChallengeById = (challengeId: number) => {
|
|
for (const challenge of Object.values(challenges)) {
|
|
if (challenge.id === challengeId) {
|
|
return challenge
|
|
}
|
|
}
|
|
logger.warn('Missing challenge with id: ' + challengeId)
|
|
}
|
|
|
|
export const solveFindIt = async function (key: ChallengeKey, isRestore: boolean = false) {
|
|
const solvedChallenge = challenges[key]
|
|
await ChallengeModel.update({ codingChallengeStatus: 1 }, { where: { key, codingChallengeStatus: { [Op.lt]: 2 } } })
|
|
logger.info(`${isRestore ? colors.grey('Restored') : colors.green('Solved')} 'Find It' phase of coding challenge ${colors.cyan(solvedChallenge.key)} (${solvedChallenge.name})`)
|
|
if (!isRestore) {
|
|
accuracy.storeFindItVerdict(solvedChallenge.key, true)
|
|
accuracy.calculateFindItAccuracy(solvedChallenge.key)
|
|
await calculateFindItCheatScore(solvedChallenge)
|
|
sendCodingChallengeNotification({ key, codingChallengeStatus: 1 })
|
|
}
|
|
}
|
|
|
|
export const solveFixIt = async function (key: ChallengeKey, isRestore: boolean = false) {
|
|
const solvedChallenge = challenges[key]
|
|
await ChallengeModel.update({ codingChallengeStatus: 2 }, { where: { key } })
|
|
logger.info(`${isRestore ? colors.grey('Restored') : colors.green('Solved')} 'Fix It' phase of coding challenge ${colors.cyan(solvedChallenge.key)} (${solvedChallenge.name})`)
|
|
if (!isRestore) {
|
|
accuracy.storeFixItVerdict(solvedChallenge.key, true)
|
|
accuracy.calculateFixItAccuracy(solvedChallenge.key)
|
|
await calculateFixItCheatScore(solvedChallenge)
|
|
sendCodingChallengeNotification({ key, codingChallengeStatus: 2 })
|
|
}
|
|
}
|