@b01lers/ctfd-api 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +8 -1
- package/dist/types.d.ts +58 -4
- package/package.json +1 -1
- package/src/client.ts +1 -1
- package/src/types.ts +8 -2
- package/tests/rate-limit.ts +19 -0
package/README.md
CHANGED
|
@@ -48,10 +48,10 @@ const challs = await client.getChallenges();
|
|
|
48
48
|
const scoreboard = await client.getScoreboard();
|
|
49
49
|
console.log(scoreboard.slice(0, 5));
|
|
50
50
|
|
|
51
|
-
// Get details about a challenge, and
|
|
51
|
+
// Get details about a challenge, and submit a flag
|
|
52
52
|
const chall = challs.find((c) => c.name === 'The Lost Park')!;
|
|
53
53
|
const details = await client.getChallengeDetails(chall.id);
|
|
54
54
|
console.log(details.description);
|
|
55
55
|
|
|
56
|
-
await client.submitFlag(chall.id, '
|
|
56
|
+
await client.submitFlag(chall.id, 'ctfd{test_flag}');
|
|
57
57
|
```
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,13 @@ class CTFdClient {
|
|
|
39
39
|
})).json();
|
|
40
40
|
return res.data;
|
|
41
41
|
}
|
|
42
|
+
async getChallengeDetails(id) {
|
|
43
|
+
const { session } = await this.getAuthedSessionNonce();
|
|
44
|
+
const res = await (await fetch(`${this.url}/api/v1/challenges/${id}`, {
|
|
45
|
+
headers: { cookie: session }
|
|
46
|
+
})).json();
|
|
47
|
+
return res.data;
|
|
48
|
+
}
|
|
42
49
|
async getScoreboard() {
|
|
43
50
|
const { session, nonce } = await this.getAuthedSessionNonce();
|
|
44
51
|
const res = await (await fetch(`${this.url}/api/v1/scoreboard`, {
|
|
@@ -55,7 +62,7 @@ class CTFdClient {
|
|
|
55
62
|
const res = await fetch(`${this.url}/login`);
|
|
56
63
|
const [session] = res.headers.getSetCookie()[0].split("; ");
|
|
57
64
|
const nonce = extractNonce(await res.text());
|
|
58
|
-
const formData = new
|
|
65
|
+
const formData = new URLSearchParams();
|
|
59
66
|
formData.append("name", this.username);
|
|
60
67
|
formData.append("password", this.password);
|
|
61
68
|
formData.append("_submit", "Submit");
|
package/dist/types.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ type BaseScoreboardEntry = {
|
|
|
5
5
|
oauth_id: null;
|
|
6
6
|
name: string;
|
|
7
7
|
score: number;
|
|
8
|
-
bracket_id: null;
|
|
9
|
-
bracket_name: null;
|
|
8
|
+
bracket_id: number | null;
|
|
9
|
+
bracket_name: string | null;
|
|
10
10
|
};
|
|
11
11
|
type ScoreboardUserEntry = BaseScoreboardEntry & {
|
|
12
12
|
account_type: "user";
|
|
@@ -23,9 +23,10 @@ type ScoreboardTeamEntry = BaseScoreboardEntry & {
|
|
|
23
23
|
}[];
|
|
24
24
|
};
|
|
25
25
|
type ScoreboardEntry = ScoreboardUserEntry | ScoreboardTeamEntry;
|
|
26
|
+
type ChallengeType = 'standard' | 'multiple_choice' | 'code';
|
|
26
27
|
type ChallengeData = {
|
|
27
28
|
id: number;
|
|
28
|
-
type:
|
|
29
|
+
type: ChallengeType;
|
|
29
30
|
name: string;
|
|
30
31
|
category: string;
|
|
31
32
|
script: string;
|
|
@@ -35,6 +36,49 @@ type ChallengeData = {
|
|
|
35
36
|
template: string;
|
|
36
37
|
value: number;
|
|
37
38
|
};
|
|
39
|
+
type BaseChallengeDetails = {
|
|
40
|
+
id: number;
|
|
41
|
+
name: string;
|
|
42
|
+
value: number;
|
|
43
|
+
description: string;
|
|
44
|
+
category: string;
|
|
45
|
+
state: "visible";
|
|
46
|
+
max_attempts: number;
|
|
47
|
+
type_data: {
|
|
48
|
+
id: ChallengeType;
|
|
49
|
+
name: ChallengeType;
|
|
50
|
+
templates: {
|
|
51
|
+
create: string;
|
|
52
|
+
update: string;
|
|
53
|
+
view: string;
|
|
54
|
+
};
|
|
55
|
+
scripts: {
|
|
56
|
+
create: string;
|
|
57
|
+
update: string;
|
|
58
|
+
view: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
solves: number;
|
|
62
|
+
solved_by_me: boolean;
|
|
63
|
+
attempts: number;
|
|
64
|
+
files: string[];
|
|
65
|
+
tags: string[];
|
|
66
|
+
hints: string[];
|
|
67
|
+
view: string;
|
|
68
|
+
};
|
|
69
|
+
type StandardChallengeDetails = BaseChallengeDetails & {
|
|
70
|
+
type: Exclude<ChallengeType, 'code'>;
|
|
71
|
+
attribution: string | null;
|
|
72
|
+
connection_info: string | null;
|
|
73
|
+
next_id: number | null;
|
|
74
|
+
};
|
|
75
|
+
type ProgrammingChallengeDetails = BaseChallengeDetails & {
|
|
76
|
+
type: Extract<ChallengeType, 'code'>;
|
|
77
|
+
language: string;
|
|
78
|
+
version: null;
|
|
79
|
+
output_enabled: null;
|
|
80
|
+
};
|
|
81
|
+
type ChallengeDetails = StandardChallengeDetails | ProgrammingChallengeDetails;
|
|
38
82
|
|
|
39
83
|
type ClientOptions = {
|
|
40
84
|
url: string;
|
|
@@ -55,10 +99,20 @@ declare class CTFdClient {
|
|
|
55
99
|
} | {
|
|
56
100
|
status: "correct";
|
|
57
101
|
message: "Correct";
|
|
102
|
+
} | {
|
|
103
|
+
status: "already_solved";
|
|
104
|
+
message: "You already solved this";
|
|
105
|
+
} | {
|
|
106
|
+
status: "paused";
|
|
107
|
+
message: `${string} is paused`;
|
|
108
|
+
} | {
|
|
109
|
+
status: "ratelimited";
|
|
110
|
+
message: "You're submitting flags too fast. Slow down.";
|
|
58
111
|
}>;
|
|
59
112
|
getChallenges(): Promise<ChallengeData[]>;
|
|
113
|
+
getChallengeDetails(id: number): Promise<ChallengeDetails>;
|
|
60
114
|
getScoreboard(): Promise<ScoreboardEntry[]>;
|
|
61
115
|
private getAuthedSessionNonce;
|
|
62
116
|
}
|
|
63
117
|
|
|
64
|
-
export { CTFdClient, type ChallengeData, type ScoreboardEntry };
|
|
118
|
+
export { CTFdClient, type ChallengeData, type ChallengeDetails, type ChallengeType, type ScoreboardEntry };
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -87,7 +87,7 @@ export class CTFdClient {
|
|
|
87
87
|
const [session] = res.headers.getSetCookie()[0].split('; ');
|
|
88
88
|
const nonce = extractNonce(await res.text());
|
|
89
89
|
|
|
90
|
-
const formData = new
|
|
90
|
+
const formData = new URLSearchParams();
|
|
91
91
|
formData.append('name', this.username);
|
|
92
92
|
formData.append('password', this.password);
|
|
93
93
|
formData.append('_submit', 'Submit');
|
package/src/types.ts
CHANGED
|
@@ -12,8 +12,8 @@ type BaseScoreboardEntry = {
|
|
|
12
12
|
oauth_id: null,
|
|
13
13
|
name: string,
|
|
14
14
|
score: number,
|
|
15
|
-
bracket_id: null,
|
|
16
|
-
bracket_name: null
|
|
15
|
+
bracket_id: number | null,
|
|
16
|
+
bracket_name: string | null // e.g. "Open Bracket"
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
type ScoreboardUserEntry = BaseScoreboardEntry & {
|
|
@@ -100,4 +100,10 @@ export type FlagSubmissionResponse = APISuccess<{
|
|
|
100
100
|
} | {
|
|
101
101
|
status: 'already_solved',
|
|
102
102
|
message: 'You already solved this'
|
|
103
|
+
} | {
|
|
104
|
+
status: 'paused',
|
|
105
|
+
message: `${string} is paused`
|
|
106
|
+
} | {
|
|
107
|
+
status: 'ratelimited',
|
|
108
|
+
message: 'You\'re submitting flags too fast. Slow down.'
|
|
103
109
|
}>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CTFdClient } from '../src/client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
;(async () => {
|
|
5
|
+
const client = new CTFdClient({
|
|
6
|
+
url: 'https://demo.ctfd.io/',
|
|
7
|
+
username: 'user',
|
|
8
|
+
password: 'password',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const chall = (await client.getChallenges()).find((c) => c.name === 'Too Many Puppers')!;
|
|
12
|
+
|
|
13
|
+
const res = await client.submitFlag(chall.id, 'test');
|
|
14
|
+
console.log(res);
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < 20; i++) {
|
|
17
|
+
console.log(await client.submitFlag(chall.id, 'test'));
|
|
18
|
+
}
|
|
19
|
+
})()
|