AI Implementation feature(906): Challenges Page and Challenge Solve Modal 1.02 (#48)
This commit was merged in pull request #48.
This commit is contained in:
@@ -17,6 +17,7 @@ import { AddCategoryTimestampsAndUniqueAbbrev1700000000200 } from './migrations/
|
||||
import { UpdateSystemCategoryKeys1700000000300 } from './migrations/1700000000300-UpdateSystemCategoryKeys';
|
||||
import { RepairCategorySchemaAndSystemCategories1700000000400 } from './migrations/1700000000400-RepairCategorySchemaAndSystemCategories';
|
||||
import { UpgradeChallengeAdminSchema1700000000500 } from './migrations/1700000000500-UpgradeChallengeAdminSchema';
|
||||
import { SeedSampleChallenges1700000000600 } from './migrations/1700000000600-SeedSampleChallenges';
|
||||
import { DatabaseInitService } from './database-init.service';
|
||||
|
||||
const ENTITIES = [
|
||||
@@ -37,6 +38,7 @@ const MIGRATIONS = [
|
||||
UpdateSystemCategoryKeys1700000000300,
|
||||
RepairCategorySchemaAndSystemCategories1700000000400,
|
||||
UpgradeChallengeAdminSchema1700000000500,
|
||||
SeedSampleChallenges1700000000600,
|
||||
];
|
||||
|
||||
@Global()
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
interface SampleChallenge {
|
||||
name: string;
|
||||
systemKey: 'CRY' | 'HW' | 'MSC' | 'PWN' | 'REV' | 'WEB';
|
||||
descriptionMd: string;
|
||||
difficulty: 'LOW' | 'MEDIUM' | 'HIGH';
|
||||
initialPoints: number;
|
||||
minimumPoints: number;
|
||||
decaySolves: number;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
const SAMPLE_CHALLENGES: SampleChallenge[] = [
|
||||
{
|
||||
name: 'Alpha Cipher',
|
||||
systemKey: 'CRY',
|
||||
descriptionMd: '# Alpha Cipher\n\nA gentle warm-up for the cryptography track.',
|
||||
difficulty: 'LOW',
|
||||
initialPoints: 200,
|
||||
minimumPoints: 100,
|
||||
decaySolves: 5,
|
||||
flag: 'flag{alpha}',
|
||||
},
|
||||
{
|
||||
name: 'Beta Cipher',
|
||||
systemKey: 'CRY',
|
||||
descriptionMd: '# Beta Cipher\n\nClassical substitution with a twist.',
|
||||
difficulty: 'MEDIUM',
|
||||
initialPoints: 300,
|
||||
minimumPoints: 100,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{beta}',
|
||||
},
|
||||
{
|
||||
name: 'Zeta Key',
|
||||
systemKey: 'CRY',
|
||||
descriptionMd: '# Zeta Key\n\nA tougher modular arithmetic problem.',
|
||||
difficulty: 'HIGH',
|
||||
initialPoints: 500,
|
||||
minimumPoints: 100,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{zeta}',
|
||||
},
|
||||
{
|
||||
name: 'Web Welcome',
|
||||
systemKey: 'WEB',
|
||||
descriptionMd: '# Web Welcome\n\nInspect the page source to find the flag.',
|
||||
difficulty: 'LOW',
|
||||
initialPoints: 100,
|
||||
minimumPoints: 50,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{web1}',
|
||||
},
|
||||
{
|
||||
name: 'Mobile Mayhem',
|
||||
systemKey: 'PWN',
|
||||
descriptionMd: '# Mobile Mayhem\n\nExploit a small userspace service.',
|
||||
difficulty: 'MEDIUM',
|
||||
initialPoints: 300,
|
||||
minimumPoints: 100,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{mob1}',
|
||||
},
|
||||
{
|
||||
name: 'Hardware Hello',
|
||||
systemKey: 'HW',
|
||||
descriptionMd: '# Hardware Hello\n\nRead the serial console output.',
|
||||
difficulty: 'LOW',
|
||||
initialPoints: 100,
|
||||
minimumPoints: 50,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{hw1}',
|
||||
},
|
||||
{
|
||||
name: 'Markdown Mystery',
|
||||
systemKey: 'MSC',
|
||||
descriptionMd: '# Markdown Mystery\n\nA flag is hidden somewhere in this README.',
|
||||
difficulty: 'LOW',
|
||||
initialPoints: 100,
|
||||
minimumPoints: 50,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{md}',
|
||||
},
|
||||
{
|
||||
name: 'Reverse Ranger',
|
||||
systemKey: 'REV',
|
||||
descriptionMd: '# Reverse Ranger\n\nDisassemble the binary and recover the key.',
|
||||
difficulty: 'MEDIUM',
|
||||
initialPoints: 300,
|
||||
minimumPoints: 100,
|
||||
decaySolves: 10,
|
||||
flag: 'flag{rev1}',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Seeds a small, representative set of sample challenges so a fresh
|
||||
* database immediately has a populated board (Job 906). Idempotent: if
|
||||
* the `challenge` table already contains any rows, the migration is a
|
||||
* no-op so manually-imported challenges are never overwritten.
|
||||
*
|
||||
* Categories are looked up by `system_key` rather than UUID so the seed
|
||||
* works regardless of the UUIDs the category migration assigned at
|
||||
* install time.
|
||||
*/
|
||||
export class SeedSampleChallenges1700000000600 implements MigrationInterface {
|
||||
name = 'SeedSampleChallenges1700000000600';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const countRows: any[] = await queryRunner.query(`SELECT COUNT(*) AS c FROM "challenge"`);
|
||||
const existing = Number(countRows?.[0]?.c ?? 0);
|
||||
if (existing > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const catRows: any[] = await queryRunner.query(
|
||||
`SELECT "id", "system_key" FROM "category" WHERE "system_key" IS NOT NULL`,
|
||||
);
|
||||
const idByKey = new Map<string, string>();
|
||||
for (const row of catRows) {
|
||||
if (row?.system_key && row?.id) {
|
||||
idByKey.set(String(row.system_key), String(row.id));
|
||||
}
|
||||
}
|
||||
if (idByKey.size < 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const sample of SAMPLE_CHALLENGES) {
|
||||
const categoryId = idByKey.get(sample.systemKey);
|
||||
if (!categoryId) continue;
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "challenge" (
|
||||
"id","name","description_md","category_id","difficulty",
|
||||
"initial_points","minimum_points","decay_solves","flag","protocol",
|
||||
"port","ip_address","enabled","created_at","updated_at"
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,strftime('%Y-%m-%dT%H:%M:%fZ','now'),strftime('%Y-%m-%dT%H:%M:%fZ','now'))`,
|
||||
[
|
||||
uuid(),
|
||||
sample.name,
|
||||
sample.descriptionMd,
|
||||
categoryId,
|
||||
sample.difficulty,
|
||||
sample.initialPoints,
|
||||
sample.minimumPoints,
|
||||
sample.decaySolves,
|
||||
sample.flag,
|
||||
'WEB',
|
||||
null,
|
||||
'',
|
||||
1,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const names = SAMPLE_CHALLENGES.map((s) => s.name);
|
||||
if (names.length === 0) return;
|
||||
const placeholders = names.map(() => '?').join(',');
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "challenge" WHERE "name" IN (${placeholders})`,
|
||||
names,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user