@b01lers/ctfd-api 1.2.1 → 2.0.0
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/dist/index.js +157 -35
- package/dist/types.d.ts +215 -42
- package/package.json +1 -1
- package/src/client.ts +24 -52
- package/src/endpoints/challenges.ts +81 -0
- package/src/endpoints/scoreboard.ts +28 -0
- package/src/endpoints/users.ts +89 -0
- package/src/index.ts +4 -1
- package/src/types/api.ts +4 -0
- package/src/{types.ts → types/challenges.ts} +3 -53
- package/src/types/scoreboard.ts +45 -0
- package/src/types/users.ts +36 -0
- package/tests/demo.ts +13 -4
- package/tests/rate-limit.ts +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,147 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
function createChallengesMethods(client) {
|
|
4
|
+
return {
|
|
5
|
+
/**
|
|
6
|
+
* Attempts to submit a flag for the given challenge.
|
|
7
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/post_challenge_attempt}
|
|
8
|
+
*
|
|
9
|
+
* @param id The ID of the challenge to submit a flag for.
|
|
10
|
+
* @param flag The flag to submit.
|
|
11
|
+
* @returns The status of the flag submission.
|
|
12
|
+
*/
|
|
13
|
+
async submitFlag(id, flag) {
|
|
14
|
+
const { session, nonce } = await client.getAuthedSessionNonce();
|
|
15
|
+
const res = await (await fetch(`${client.url}/api/v1/challenges/attempt`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
"Csrf-Token": nonce,
|
|
20
|
+
cookie: session
|
|
21
|
+
},
|
|
22
|
+
body: JSON.stringify({ challenge_id: id, submission: flag })
|
|
23
|
+
})).json();
|
|
24
|
+
return res.data;
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* Fetches the list of all challenges.
|
|
28
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge_list}
|
|
29
|
+
*
|
|
30
|
+
* @returns The list of challenges, as a `Challenge[]`.
|
|
31
|
+
*/
|
|
32
|
+
async get() {
|
|
33
|
+
return client.callApi("/challenges");
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Fetches details for the given challenge.
|
|
37
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge}.
|
|
38
|
+
*
|
|
39
|
+
* @param id The ID of the challenge to fetch.
|
|
40
|
+
* @returns The challenge details.
|
|
41
|
+
*/
|
|
42
|
+
async getDetails(id) {
|
|
43
|
+
return client.callApi(`/challenges/${id}`);
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* Fetches solves for the given challenge.
|
|
47
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge_solves}.
|
|
48
|
+
*
|
|
49
|
+
* @param id The ID of the challenge to fetch solves for.
|
|
50
|
+
* @returns The challenge's solves.
|
|
51
|
+
*/
|
|
52
|
+
async getSolves(id) {
|
|
53
|
+
return client.callApi(`/challenges/${id}/solves`);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createScoreboardMethods(client) {
|
|
59
|
+
return {
|
|
60
|
+
/**
|
|
61
|
+
* Fetches the list of scoreboard entries.
|
|
62
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/scoreboard/operation/get_scoreboard_list}
|
|
63
|
+
*
|
|
64
|
+
* @returns The scoreboard, as a `ScoreboardEntry[]`.
|
|
65
|
+
*/
|
|
66
|
+
async get() {
|
|
67
|
+
return client.callApi("/scoreboard");
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Fetches details for the top `n` teams on the scoreboard.
|
|
71
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/scoreboard/operation/get_scoreboard_detail}
|
|
72
|
+
*
|
|
73
|
+
* @param count The number of teams to fetch.
|
|
74
|
+
* @returns The scoreboard details.
|
|
75
|
+
*/
|
|
76
|
+
async getTop(count) {
|
|
77
|
+
return client.callApi(`/scoreboard/top/${count}`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function createUsersMethods(client) {
|
|
83
|
+
return {
|
|
84
|
+
me: {
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves the currently logged-in user.
|
|
87
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private}
|
|
88
|
+
* @returns The logged-in user.
|
|
89
|
+
*/
|
|
90
|
+
async get() {
|
|
91
|
+
return client.callApi("/users/me");
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Retrieves solves for the currently logged-in user.
|
|
95
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private_solves}
|
|
96
|
+
*
|
|
97
|
+
* @returns The logged-in user's solves.
|
|
98
|
+
*/
|
|
99
|
+
async getSolves() {
|
|
100
|
+
return client.callApi("/users/me/solves");
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* Retrieves awards for the currently logged-in user.
|
|
104
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private_awards}
|
|
105
|
+
*
|
|
106
|
+
* @returns The logged-in user's awards.
|
|
107
|
+
*/
|
|
108
|
+
async getAwards() {
|
|
109
|
+
return client.callApi("/users/me/awards");
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* Retrieves a user by ID.
|
|
114
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public}
|
|
115
|
+
*
|
|
116
|
+
* @param id The ID of the user to retrieve.
|
|
117
|
+
* @returns The fetched user.
|
|
118
|
+
*/
|
|
119
|
+
async getById(id) {
|
|
120
|
+
return client.callApi(`/users/${id}`);
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* Retrieves solves for a given user.
|
|
124
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public_solves}
|
|
125
|
+
*
|
|
126
|
+
* @param id The ID of the user to retrieve solves for.
|
|
127
|
+
* @returns The user's solves.
|
|
128
|
+
*/
|
|
129
|
+
async getSolves(id) {
|
|
130
|
+
return client.callApi(`/users/${id}/solves`);
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Retrieves awards for a given user.
|
|
134
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public_awards}
|
|
135
|
+
*
|
|
136
|
+
* @param id The ID of the user to retrieve awards for.
|
|
137
|
+
* @returns The user's awards.
|
|
138
|
+
*/
|
|
139
|
+
async getAwards(id) {
|
|
140
|
+
return client.callApi(`/users/${id}/awards`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
3
145
|
function extractNonce(raw) {
|
|
4
146
|
return raw.match(/'csrfNonce': "(.+?)"/)[1];
|
|
5
147
|
}
|
|
@@ -15,40 +157,19 @@ class CTFdClient {
|
|
|
15
157
|
__publicField(this, "cachedSession", null);
|
|
16
158
|
__publicField(this, "cachedNonce", null);
|
|
17
159
|
__publicField(this, "sessionExpiry", /* @__PURE__ */ new Date());
|
|
160
|
+
__publicField(this, "challenges");
|
|
161
|
+
__publicField(this, "scoreboard");
|
|
162
|
+
__publicField(this, "users");
|
|
18
163
|
this.url = options.url.endsWith("/") ? options.url.slice(0, -1) : options.url;
|
|
19
164
|
this.username = options.username;
|
|
20
165
|
this.password = options.password;
|
|
166
|
+
this.challenges = createChallengesMethods(this);
|
|
167
|
+
this.scoreboard = createScoreboardMethods(this);
|
|
168
|
+
this.users = createUsersMethods(this);
|
|
21
169
|
}
|
|
22
|
-
async
|
|
23
|
-
const { session, nonce } = await this.getAuthedSessionNonce();
|
|
24
|
-
const res = await (await fetch(`${this.url}/api/v1/challenges/attempt`, {
|
|
25
|
-
method: "POST",
|
|
26
|
-
headers: {
|
|
27
|
-
"Content-Type": "application/json",
|
|
28
|
-
"Csrf-Token": nonce,
|
|
29
|
-
cookie: session
|
|
30
|
-
},
|
|
31
|
-
body: JSON.stringify({ challenge_id: id, submission: flag })
|
|
32
|
-
})).json();
|
|
33
|
-
return res.data;
|
|
34
|
-
}
|
|
35
|
-
async getChallenges() {
|
|
36
|
-
const { session } = await this.getAuthedSessionNonce();
|
|
37
|
-
const res = await (await fetch(`${this.url}/api/v1/challenges`, {
|
|
38
|
-
headers: { cookie: session }
|
|
39
|
-
})).json();
|
|
40
|
-
return res.data;
|
|
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
|
-
}
|
|
49
|
-
async getScoreboard() {
|
|
170
|
+
async callApi(endpoint) {
|
|
50
171
|
const { session, nonce } = await this.getAuthedSessionNonce();
|
|
51
|
-
const res = await (await fetch(`${this.url}/api/v1
|
|
172
|
+
const res = await (await fetch(`${this.url}/api/v1${endpoint}`, {
|
|
52
173
|
headers: {
|
|
53
174
|
"Csrf-Token": nonce,
|
|
54
175
|
cookie: session
|
|
@@ -56,22 +177,23 @@ class CTFdClient {
|
|
|
56
177
|
})).json();
|
|
57
178
|
return res.data;
|
|
58
179
|
}
|
|
180
|
+
// TODO
|
|
59
181
|
async getAuthedSessionNonce() {
|
|
60
182
|
if (/* @__PURE__ */ new Date() < this.sessionExpiry && this.cachedSession && this.cachedNonce)
|
|
61
183
|
return { session: this.cachedSession, nonce: this.cachedNonce };
|
|
62
184
|
const res = await fetch(`${this.url}/login`);
|
|
63
185
|
const [session] = res.headers.getSetCookie()[0].split("; ");
|
|
64
186
|
const nonce = extractNonce(await res.text());
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
187
|
+
const params = new URLSearchParams();
|
|
188
|
+
params.append("name", this.username);
|
|
189
|
+
params.append("password", this.password);
|
|
190
|
+
params.append("_submit", "Submit");
|
|
191
|
+
params.append("nonce", nonce);
|
|
70
192
|
const loginRes = await fetch(`${this.url}/login`, {
|
|
71
193
|
method: "POST",
|
|
72
194
|
headers: { cookie: session },
|
|
73
195
|
redirect: "manual",
|
|
74
|
-
body:
|
|
196
|
+
body: params
|
|
75
197
|
});
|
|
76
198
|
const [authedSession, expiresGmt] = loginRes.headers.getSetCookie()[0].split("; ");
|
|
77
199
|
const authedRaw = await (await fetch(`${this.url}/challenges`, {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,30 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
pos: number;
|
|
1
|
+
interface Solve {
|
|
3
2
|
account_id: number;
|
|
4
3
|
account_url: string;
|
|
5
|
-
|
|
4
|
+
date: string;
|
|
6
5
|
name: string;
|
|
7
|
-
|
|
8
|
-
bracket_id: number | null;
|
|
9
|
-
bracket_name: string | null;
|
|
10
|
-
};
|
|
11
|
-
type ScoreboardUserEntry = BaseScoreboardEntry & {
|
|
12
|
-
account_type: "user";
|
|
13
|
-
};
|
|
14
|
-
type ScoreboardTeamEntry = BaseScoreboardEntry & {
|
|
15
|
-
account_type: "team";
|
|
16
|
-
members: {
|
|
17
|
-
id: number;
|
|
18
|
-
oauth_id: null;
|
|
19
|
-
name: string;
|
|
20
|
-
score: number;
|
|
21
|
-
bracket_id: null;
|
|
22
|
-
bracket_name: null;
|
|
23
|
-
}[];
|
|
24
|
-
};
|
|
25
|
-
type ScoreboardEntry = ScoreboardUserEntry | ScoreboardTeamEntry;
|
|
6
|
+
}
|
|
26
7
|
type ChallengeType = 'standard' | 'multiple_choice' | 'code';
|
|
27
|
-
|
|
8
|
+
interface Challenge {
|
|
28
9
|
id: number;
|
|
29
10
|
type: ChallengeType;
|
|
30
11
|
name: string;
|
|
@@ -35,7 +16,7 @@ type ChallengeData = {
|
|
|
35
16
|
tags: string[];
|
|
36
17
|
template: string;
|
|
37
18
|
value: number;
|
|
38
|
-
}
|
|
19
|
+
}
|
|
39
20
|
type BaseChallengeDetails = {
|
|
40
21
|
id: number;
|
|
41
22
|
name: string;
|
|
@@ -80,19 +61,15 @@ type ProgrammingChallengeDetails = BaseChallengeDetails & {
|
|
|
80
61
|
};
|
|
81
62
|
type ChallengeDetails = StandardChallengeDetails | ProgrammingChallengeDetails;
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
private cachedSession;
|
|
93
|
-
private cachedNonce;
|
|
94
|
-
private sessionExpiry;
|
|
95
|
-
constructor(options: ClientOptions);
|
|
64
|
+
declare function createChallengesMethods(client: CTFdClient): {
|
|
65
|
+
/**
|
|
66
|
+
* Attempts to submit a flag for the given challenge.
|
|
67
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/post_challenge_attempt}
|
|
68
|
+
*
|
|
69
|
+
* @param id The ID of the challenge to submit a flag for.
|
|
70
|
+
* @param flag The flag to submit.
|
|
71
|
+
* @returns The status of the flag submission.
|
|
72
|
+
*/
|
|
96
73
|
submitFlag(id: number, flag: string): Promise<{
|
|
97
74
|
status: "incorrect";
|
|
98
75
|
message: "Incorrect";
|
|
@@ -109,10 +86,206 @@ declare class CTFdClient {
|
|
|
109
86
|
status: "ratelimited";
|
|
110
87
|
message: "You're submitting flags too fast. Slow down.";
|
|
111
88
|
}>;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Fetches the list of all challenges.
|
|
91
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge_list}
|
|
92
|
+
*
|
|
93
|
+
* @returns The list of challenges, as a `Challenge[]`.
|
|
94
|
+
*/
|
|
95
|
+
get(): Promise<Challenge[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Fetches details for the given challenge.
|
|
98
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge}.
|
|
99
|
+
*
|
|
100
|
+
* @param id The ID of the challenge to fetch.
|
|
101
|
+
* @returns The challenge details.
|
|
102
|
+
*/
|
|
103
|
+
getDetails(id: number): Promise<ChallengeDetails>;
|
|
104
|
+
/**
|
|
105
|
+
* Fetches solves for the given challenge.
|
|
106
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge_solves}.
|
|
107
|
+
*
|
|
108
|
+
* @param id The ID of the challenge to fetch solves for.
|
|
109
|
+
* @returns The challenge's solves.
|
|
110
|
+
*/
|
|
111
|
+
getSolves(id: number): Promise<Solve[]>;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type BaseScoreboardEntry = {
|
|
115
|
+
pos: number;
|
|
116
|
+
account_id: number;
|
|
117
|
+
account_url: string;
|
|
118
|
+
oauth_id: null;
|
|
119
|
+
name: string;
|
|
120
|
+
score: number;
|
|
121
|
+
bracket_id: number | null;
|
|
122
|
+
bracket_name: string | null;
|
|
123
|
+
};
|
|
124
|
+
type ScoreboardUserEntry = BaseScoreboardEntry & {
|
|
125
|
+
account_type: "user";
|
|
126
|
+
};
|
|
127
|
+
type ScoreboardTeamEntry = BaseScoreboardEntry & {
|
|
128
|
+
account_type: "team";
|
|
129
|
+
members: {
|
|
130
|
+
id: number;
|
|
131
|
+
oauth_id: null;
|
|
132
|
+
name: string;
|
|
133
|
+
score: number;
|
|
134
|
+
bracket_id: null;
|
|
135
|
+
bracket_name: null;
|
|
136
|
+
}[];
|
|
137
|
+
};
|
|
138
|
+
type ScoreboardEntry = ScoreboardUserEntry | ScoreboardTeamEntry;
|
|
139
|
+
interface ScoreboardDetails {
|
|
140
|
+
id: number;
|
|
141
|
+
account_url: string;
|
|
142
|
+
name: string;
|
|
143
|
+
score: number;
|
|
144
|
+
bracket_id: null;
|
|
145
|
+
bracket_name: null;
|
|
146
|
+
solves: {
|
|
147
|
+
challenge_id: number;
|
|
148
|
+
account_id: number;
|
|
149
|
+
team_id: number | null;
|
|
150
|
+
user_id: number;
|
|
151
|
+
value: number;
|
|
152
|
+
date: string;
|
|
153
|
+
}[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
declare function createScoreboardMethods(client: CTFdClient): {
|
|
157
|
+
/**
|
|
158
|
+
* Fetches the list of scoreboard entries.
|
|
159
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/scoreboard/operation/get_scoreboard_list}
|
|
160
|
+
*
|
|
161
|
+
* @returns The scoreboard, as a `ScoreboardEntry[]`.
|
|
162
|
+
*/
|
|
163
|
+
get(): Promise<ScoreboardEntry[]>;
|
|
164
|
+
/**
|
|
165
|
+
* Fetches details for the top `n` teams on the scoreboard.
|
|
166
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/scoreboard/operation/get_scoreboard_detail}
|
|
167
|
+
*
|
|
168
|
+
* @param count The number of teams to fetch.
|
|
169
|
+
* @returns The scoreboard details.
|
|
170
|
+
*/
|
|
171
|
+
getTop(count: number): Promise<ScoreboardDetails[]>;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
interface User {
|
|
175
|
+
id: number;
|
|
176
|
+
oauth_id: number | null;
|
|
177
|
+
name: string;
|
|
178
|
+
email?: string;
|
|
179
|
+
website: string | null;
|
|
180
|
+
affiliation: string | null;
|
|
181
|
+
country: string | null;
|
|
182
|
+
bracket_id: number | null;
|
|
183
|
+
language: string | null;
|
|
184
|
+
team_id: number | null;
|
|
185
|
+
created: string;
|
|
186
|
+
fields: [];
|
|
187
|
+
}
|
|
188
|
+
interface Award {
|
|
189
|
+
description: string | null;
|
|
190
|
+
date: string;
|
|
191
|
+
id: number;
|
|
192
|
+
category: string;
|
|
193
|
+
user_id: number;
|
|
194
|
+
team_id: null;
|
|
195
|
+
name: string;
|
|
196
|
+
user: number;
|
|
197
|
+
team: null;
|
|
198
|
+
value: number;
|
|
199
|
+
icon: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface UserSolve {
|
|
203
|
+
user: {
|
|
204
|
+
name: string;
|
|
205
|
+
id: number;
|
|
206
|
+
};
|
|
207
|
+
date: string;
|
|
208
|
+
challenge_id: number;
|
|
209
|
+
challenge: {
|
|
210
|
+
value: number;
|
|
211
|
+
name: string;
|
|
212
|
+
id: number;
|
|
213
|
+
category: string;
|
|
214
|
+
};
|
|
215
|
+
team: number | null;
|
|
216
|
+
id: number;
|
|
217
|
+
type: "correct";
|
|
218
|
+
}
|
|
219
|
+
declare function createUsersMethods(client: CTFdClient): {
|
|
220
|
+
me: {
|
|
221
|
+
/**
|
|
222
|
+
* Retrieves the currently logged-in user.
|
|
223
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private}
|
|
224
|
+
* @returns The logged-in user.
|
|
225
|
+
*/
|
|
226
|
+
get(): Promise<User>;
|
|
227
|
+
/**
|
|
228
|
+
* Retrieves solves for the currently logged-in user.
|
|
229
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private_solves}
|
|
230
|
+
*
|
|
231
|
+
* @returns The logged-in user's solves.
|
|
232
|
+
*/
|
|
233
|
+
getSolves(): Promise<UserSolve[]>;
|
|
234
|
+
/**
|
|
235
|
+
* Retrieves awards for the currently logged-in user.
|
|
236
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private_awards}
|
|
237
|
+
*
|
|
238
|
+
* @returns The logged-in user's awards.
|
|
239
|
+
*/
|
|
240
|
+
getAwards(): Promise<Award[]>;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Retrieves a user by ID.
|
|
244
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public}
|
|
245
|
+
*
|
|
246
|
+
* @param id The ID of the user to retrieve.
|
|
247
|
+
* @returns The fetched user.
|
|
248
|
+
*/
|
|
249
|
+
getById(id: number): Promise<User>;
|
|
250
|
+
/**
|
|
251
|
+
* Retrieves solves for a given user.
|
|
252
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public_solves}
|
|
253
|
+
*
|
|
254
|
+
* @param id The ID of the user to retrieve solves for.
|
|
255
|
+
* @returns The user's solves.
|
|
256
|
+
*/
|
|
257
|
+
getSolves(id: number): Promise<UserSolve[]>;
|
|
258
|
+
/**
|
|
259
|
+
* Retrieves awards for a given user.
|
|
260
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public_awards}
|
|
261
|
+
*
|
|
262
|
+
* @param id The ID of the user to retrieve awards for.
|
|
263
|
+
* @returns The user's awards.
|
|
264
|
+
*/
|
|
265
|
+
getAwards(id: number): Promise<Award[]>;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
type ClientOptions = {
|
|
269
|
+
url: string;
|
|
270
|
+
username: string;
|
|
271
|
+
password: string;
|
|
272
|
+
};
|
|
273
|
+
declare class CTFdClient {
|
|
274
|
+
readonly url: string;
|
|
275
|
+
private readonly username;
|
|
276
|
+
private readonly password;
|
|
277
|
+
private cachedSession;
|
|
278
|
+
private cachedNonce;
|
|
279
|
+
private sessionExpiry;
|
|
280
|
+
readonly challenges: ReturnType<typeof createChallengesMethods>;
|
|
281
|
+
readonly scoreboard: ReturnType<typeof createScoreboardMethods>;
|
|
282
|
+
readonly users: ReturnType<typeof createUsersMethods>;
|
|
283
|
+
constructor(options: ClientOptions);
|
|
284
|
+
callApi<T>(endpoint: string): Promise<T>;
|
|
285
|
+
getAuthedSessionNonce(): Promise<{
|
|
286
|
+
session: string;
|
|
287
|
+
nonce: string;
|
|
288
|
+
}>;
|
|
116
289
|
}
|
|
117
290
|
|
|
118
|
-
export { CTFdClient, type
|
|
291
|
+
export { type Award, CTFdClient, type Challenge, type ChallengeDetails, type ChallengeType, type ScoreboardEntry, type Solve, type User };
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { createChallengesMethods } from './endpoints/challenges';
|
|
2
|
+
import { createScoreboardMethods } from './endpoints/scoreboard';
|
|
3
|
+
import { createUsersMethods } from './endpoints/users';
|
|
1
4
|
import { extractNonce } from './util';
|
|
2
|
-
import type {
|
|
3
|
-
ChallengeDetailsResponse,
|
|
4
|
-
ChallengesResponse,
|
|
5
|
-
FlagSubmissionResponse,
|
|
6
|
-
ScoreboardResponse
|
|
7
|
-
} from './types';
|
|
5
|
+
import type { APISuccess } from './types/api';
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
type ClientOptions = {
|
|
@@ -14,7 +12,7 @@ type ClientOptions = {
|
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
export class CTFdClient {
|
|
17
|
-
|
|
15
|
+
public readonly url: string;
|
|
18
16
|
private readonly username: string;
|
|
19
17
|
private readonly password: string;
|
|
20
18
|
|
|
@@ -22,62 +20,36 @@ export class CTFdClient {
|
|
|
22
20
|
private cachedNonce: string | null = null;
|
|
23
21
|
private sessionExpiry = new Date();
|
|
24
22
|
|
|
23
|
+
public readonly challenges: ReturnType<typeof createChallengesMethods>;
|
|
24
|
+
public readonly scoreboard: ReturnType<typeof createScoreboardMethods>;
|
|
25
|
+
public readonly users: ReturnType<typeof createUsersMethods>;
|
|
26
|
+
|
|
25
27
|
constructor(options: ClientOptions) {
|
|
26
28
|
this.url = options.url.endsWith('/') ? options.url.slice(0, -1) : options.url;
|
|
27
29
|
this.username = options.username;
|
|
28
30
|
this.password = options.password;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public async submitFlag(id: number, flag: string) {
|
|
32
|
-
const { session, nonce } = await this.getAuthedSessionNonce();
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'Content-Type': 'application/json',
|
|
38
|
-
'Csrf-Token': nonce,
|
|
39
|
-
cookie: session,
|
|
40
|
-
},
|
|
41
|
-
body: JSON.stringify({ challenge_id: id, submission: flag }),
|
|
42
|
-
})).json() as FlagSubmissionResponse;
|
|
43
|
-
|
|
44
|
-
return res.data;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
public async getChallenges() {
|
|
48
|
-
const { session } = await this.getAuthedSessionNonce();
|
|
49
|
-
|
|
50
|
-
const res = await (await fetch(`${this.url}/api/v1/challenges`, {
|
|
51
|
-
headers: { cookie: session }
|
|
52
|
-
})).json() as ChallengesResponse;
|
|
53
|
-
|
|
54
|
-
return res.data;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
public async getChallengeDetails(id: number) {
|
|
58
|
-
const { session } = await this.getAuthedSessionNonce();
|
|
59
|
-
|
|
60
|
-
const res = await (await fetch(`${this.url}/api/v1/challenges/${id}`, {
|
|
61
|
-
headers: { cookie: session }
|
|
62
|
-
})).json() as ChallengeDetailsResponse;
|
|
63
|
-
|
|
64
|
-
return res.data;
|
|
32
|
+
this.challenges = createChallengesMethods(this);
|
|
33
|
+
this.scoreboard = createScoreboardMethods(this);
|
|
34
|
+
this.users = createUsersMethods(this);
|
|
65
35
|
}
|
|
66
36
|
|
|
67
|
-
public async
|
|
37
|
+
public async callApi<T>(endpoint: string) {
|
|
68
38
|
const { session, nonce } = await this.getAuthedSessionNonce();
|
|
69
39
|
|
|
70
|
-
|
|
40
|
+
// TODO: error handling?
|
|
41
|
+
const res = await (await fetch(`${this.url}/api/v1${endpoint}`, {
|
|
71
42
|
headers: {
|
|
72
43
|
'Csrf-Token': nonce,
|
|
73
44
|
cookie: session,
|
|
74
45
|
},
|
|
75
|
-
})).json() as
|
|
46
|
+
})).json() as APISuccess<T>;
|
|
76
47
|
|
|
77
48
|
return res.data;
|
|
78
49
|
}
|
|
79
50
|
|
|
80
|
-
|
|
51
|
+
// TODO
|
|
52
|
+
public async getAuthedSessionNonce() {
|
|
81
53
|
// If we have a cached, non-expired session, use it
|
|
82
54
|
if (new Date() < this.sessionExpiry && this.cachedSession && this.cachedNonce)
|
|
83
55
|
return { session: this.cachedSession, nonce: this.cachedNonce };
|
|
@@ -87,17 +59,17 @@ export class CTFdClient {
|
|
|
87
59
|
const [session] = res.headers.getSetCookie()[0].split('; ');
|
|
88
60
|
const nonce = extractNonce(await res.text());
|
|
89
61
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
62
|
+
const params = new URLSearchParams();
|
|
63
|
+
params.append('name', this.username);
|
|
64
|
+
params.append('password', this.password);
|
|
65
|
+
params.append('_submit', 'Submit');
|
|
66
|
+
params.append('nonce', nonce);
|
|
95
67
|
|
|
96
68
|
const loginRes = await fetch(`${this.url}/login`, {
|
|
97
69
|
method: 'POST',
|
|
98
70
|
headers: { cookie: session },
|
|
99
71
|
redirect: 'manual',
|
|
100
|
-
body:
|
|
72
|
+
body: params,
|
|
101
73
|
});
|
|
102
74
|
|
|
103
75
|
const [authedSession, expiresGmt] = loginRes.headers.getSetCookie()[0].split('; ');
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { CTFdClient } from '../client';
|
|
2
|
+
import type { Challenge, ChallengeDetails, Solve } from '../types/challenges';
|
|
3
|
+
import type { APISuccess } from '../types/api';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export type FlagSubmissionResponse = APISuccess<{
|
|
7
|
+
status: 'incorrect',
|
|
8
|
+
message: 'Incorrect'
|
|
9
|
+
} | {
|
|
10
|
+
status: 'correct',
|
|
11
|
+
message: 'Correct'
|
|
12
|
+
} | {
|
|
13
|
+
status: 'already_solved',
|
|
14
|
+
message: 'You already solved this'
|
|
15
|
+
} | {
|
|
16
|
+
status: 'paused',
|
|
17
|
+
message: `${string} is paused`
|
|
18
|
+
} | {
|
|
19
|
+
status: 'ratelimited',
|
|
20
|
+
message: 'You\'re submitting flags too fast. Slow down.'
|
|
21
|
+
}>
|
|
22
|
+
|
|
23
|
+
export function createChallengesMethods(client: CTFdClient) {
|
|
24
|
+
return {
|
|
25
|
+
/**
|
|
26
|
+
* Attempts to submit a flag for the given challenge.
|
|
27
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/post_challenge_attempt}
|
|
28
|
+
*
|
|
29
|
+
* @param id The ID of the challenge to submit a flag for.
|
|
30
|
+
* @param flag The flag to submit.
|
|
31
|
+
* @returns The status of the flag submission.
|
|
32
|
+
*/
|
|
33
|
+
async submitFlag(id: number, flag: string) {
|
|
34
|
+
const { session, nonce } = await client.getAuthedSessionNonce();
|
|
35
|
+
|
|
36
|
+
const res = await (await fetch(`${client.url}/api/v1/challenges/attempt`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
'Csrf-Token': nonce,
|
|
41
|
+
cookie: session,
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ challenge_id: id, submission: flag }),
|
|
44
|
+
})).json() as FlagSubmissionResponse;
|
|
45
|
+
|
|
46
|
+
return res.data;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fetches the list of all challenges.
|
|
51
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge_list}
|
|
52
|
+
*
|
|
53
|
+
* @returns The list of challenges, as a `Challenge[]`.
|
|
54
|
+
*/
|
|
55
|
+
async get() {
|
|
56
|
+
return client.callApi<Challenge[]>('/challenges');
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fetches details for the given challenge.
|
|
61
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge}.
|
|
62
|
+
*
|
|
63
|
+
* @param id The ID of the challenge to fetch.
|
|
64
|
+
* @returns The challenge details.
|
|
65
|
+
*/
|
|
66
|
+
async getDetails(id: number) {
|
|
67
|
+
return client.callApi<ChallengeDetails>(`/challenges/${id}`);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Fetches solves for the given challenge.
|
|
72
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/challenges/operation/get_challenge_solves}.
|
|
73
|
+
*
|
|
74
|
+
* @param id The ID of the challenge to fetch solves for.
|
|
75
|
+
* @returns The challenge's solves.
|
|
76
|
+
*/
|
|
77
|
+
async getSolves(id: number) {
|
|
78
|
+
return client.callApi<Solve[]>(`/challenges/${id}/solves`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CTFdClient } from '../client';
|
|
2
|
+
import type { ScoreboardDetails, ScoreboardEntry } from '../types/scoreboard';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function createScoreboardMethods(client: CTFdClient) {
|
|
6
|
+
return {
|
|
7
|
+
/**
|
|
8
|
+
* Fetches the list of scoreboard entries.
|
|
9
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/scoreboard/operation/get_scoreboard_list}
|
|
10
|
+
*
|
|
11
|
+
* @returns The scoreboard, as a `ScoreboardEntry[]`.
|
|
12
|
+
*/
|
|
13
|
+
async get() {
|
|
14
|
+
return client.callApi<ScoreboardEntry[]>('/scoreboard');
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fetches details for the top `n` teams on the scoreboard.
|
|
19
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/scoreboard/operation/get_scoreboard_detail}
|
|
20
|
+
*
|
|
21
|
+
* @param count The number of teams to fetch.
|
|
22
|
+
* @returns The scoreboard details.
|
|
23
|
+
*/
|
|
24
|
+
async getTop(count: number) {
|
|
25
|
+
return client.callApi<ScoreboardDetails[]>(`/scoreboard/top/${count}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { CTFdClient } from '../client';
|
|
2
|
+
import type { Award, User } from '../types/users';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
interface UserSolve {
|
|
6
|
+
user: {
|
|
7
|
+
name: string,
|
|
8
|
+
id: number,
|
|
9
|
+
},
|
|
10
|
+
date: string, // ISO
|
|
11
|
+
challenge_id: number,
|
|
12
|
+
challenge: {
|
|
13
|
+
value: number,
|
|
14
|
+
name: string,
|
|
15
|
+
id: number,
|
|
16
|
+
category: string,
|
|
17
|
+
},
|
|
18
|
+
team: number | null,
|
|
19
|
+
id: number,
|
|
20
|
+
type: "correct" // TODO?
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createUsersMethods(client: CTFdClient) {
|
|
24
|
+
return {
|
|
25
|
+
me: {
|
|
26
|
+
/**
|
|
27
|
+
* Retrieves the currently logged-in user.
|
|
28
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private}
|
|
29
|
+
* @returns The logged-in user.
|
|
30
|
+
*/
|
|
31
|
+
async get() {
|
|
32
|
+
return client.callApi<User>('/users/me');
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Retrieves solves for the currently logged-in user.
|
|
37
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private_solves}
|
|
38
|
+
*
|
|
39
|
+
* @returns The logged-in user's solves.
|
|
40
|
+
*/
|
|
41
|
+
async getSolves() {
|
|
42
|
+
return client.callApi<UserSolve[]>('/users/me/solves');
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Retrieves awards for the currently logged-in user.
|
|
47
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_private_awards}
|
|
48
|
+
*
|
|
49
|
+
* @returns The logged-in user's awards.
|
|
50
|
+
*/
|
|
51
|
+
async getAwards() {
|
|
52
|
+
return client.callApi<Award[]>('/users/me/awards');
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Retrieves a user by ID.
|
|
58
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public}
|
|
59
|
+
*
|
|
60
|
+
* @param id The ID of the user to retrieve.
|
|
61
|
+
* @returns The fetched user.
|
|
62
|
+
*/
|
|
63
|
+
async getById(id: number) {
|
|
64
|
+
return client.callApi<User>(`/users/${id}`);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Retrieves solves for a given user.
|
|
69
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public_solves}
|
|
70
|
+
*
|
|
71
|
+
* @param id The ID of the user to retrieve solves for.
|
|
72
|
+
* @returns The user's solves.
|
|
73
|
+
*/
|
|
74
|
+
async getSolves(id: number) {
|
|
75
|
+
return client.callApi<UserSolve[]>(`/users/${id}/solves`);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Retrieves awards for a given user.
|
|
80
|
+
* Ref: {@Link https://docs.ctfd.io/docs/api/redoc#tag/users/operation/get_user_public_awards}
|
|
81
|
+
*
|
|
82
|
+
* @param id The ID of the user to retrieve awards for.
|
|
83
|
+
* @returns The user's awards.
|
|
84
|
+
*/
|
|
85
|
+
async getAwards(id: number) {
|
|
86
|
+
return client.callApi<Award[]>(`/users/${id}/awards`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { CTFdClient } from './client';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
export type { ChallengeType, Challenge, ChallengeDetails, Solve } from './types/challenges'
|
|
4
|
+
export type { ScoreboardEntry } from './types/scoreboard';
|
|
5
|
+
export type { User, Award } from './types/users';
|
package/src/types/api.ts
ADDED
|
@@ -1,43 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
success: true,
|
|
3
|
-
data: T
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export type ScoreboardResponse = APISuccess<ScoreboardEntry[]>
|
|
7
|
-
|
|
8
|
-
type BaseScoreboardEntry = {
|
|
9
|
-
pos: number,
|
|
1
|
+
export interface Solve {
|
|
10
2
|
account_id: number,
|
|
11
3
|
account_url: string,
|
|
12
|
-
|
|
4
|
+
date: string,
|
|
13
5
|
name: string,
|
|
14
|
-
score: number,
|
|
15
|
-
bracket_id: number | null,
|
|
16
|
-
bracket_name: string | null // e.g. "Open Bracket"
|
|
17
6
|
}
|
|
18
7
|
|
|
19
|
-
type ScoreboardUserEntry = BaseScoreboardEntry & {
|
|
20
|
-
account_type: "user",
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type ScoreboardTeamEntry = BaseScoreboardEntry & {
|
|
24
|
-
account_type: "team",
|
|
25
|
-
members: {
|
|
26
|
-
id: number,
|
|
27
|
-
oauth_id: null,
|
|
28
|
-
name: string,
|
|
29
|
-
score: number,
|
|
30
|
-
bracket_id: null,
|
|
31
|
-
bracket_name: null
|
|
32
|
-
}[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type ScoreboardEntry = ScoreboardUserEntry | ScoreboardTeamEntry;
|
|
36
|
-
|
|
37
|
-
export type ChallengesResponse = APISuccess<ChallengeData[]>
|
|
38
|
-
|
|
39
8
|
export type ChallengeType = 'standard' | 'multiple_choice' | 'code';
|
|
40
|
-
export
|
|
9
|
+
export interface Challenge {
|
|
41
10
|
id: number,
|
|
42
11
|
type: ChallengeType,
|
|
43
12
|
name: string,
|
|
@@ -50,8 +19,6 @@ export type ChallengeData = {
|
|
|
50
19
|
value: number,
|
|
51
20
|
}
|
|
52
21
|
|
|
53
|
-
export type ChallengeDetailsResponse = APISuccess<ChallengeDetails>;
|
|
54
|
-
|
|
55
22
|
type BaseChallengeDetails = {
|
|
56
23
|
id: number,
|
|
57
24
|
name: string,
|
|
@@ -90,20 +57,3 @@ type ProgrammingChallengeDetails = BaseChallengeDetails & {
|
|
|
90
57
|
}
|
|
91
58
|
|
|
92
59
|
export type ChallengeDetails = StandardChallengeDetails | ProgrammingChallengeDetails;
|
|
93
|
-
|
|
94
|
-
export type FlagSubmissionResponse = APISuccess<{
|
|
95
|
-
status: 'incorrect',
|
|
96
|
-
message: 'Incorrect'
|
|
97
|
-
} | {
|
|
98
|
-
status: 'correct',
|
|
99
|
-
message: 'Correct'
|
|
100
|
-
} | {
|
|
101
|
-
status: 'already_solved',
|
|
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.'
|
|
109
|
-
}>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type BaseScoreboardEntry = {
|
|
2
|
+
pos: number,
|
|
3
|
+
account_id: number,
|
|
4
|
+
account_url: string,
|
|
5
|
+
oauth_id: null, // TODO
|
|
6
|
+
name: string,
|
|
7
|
+
score: number,
|
|
8
|
+
bracket_id: number | null,
|
|
9
|
+
bracket_name: string | null // e.g. "Open Bracket"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type ScoreboardUserEntry = BaseScoreboardEntry & {
|
|
13
|
+
account_type: "user",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ScoreboardTeamEntry = BaseScoreboardEntry & {
|
|
17
|
+
account_type: "team",
|
|
18
|
+
members: {
|
|
19
|
+
id: number,
|
|
20
|
+
oauth_id: null,
|
|
21
|
+
name: string,
|
|
22
|
+
score: number,
|
|
23
|
+
bracket_id: null,
|
|
24
|
+
bracket_name: null
|
|
25
|
+
}[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ScoreboardEntry = ScoreboardUserEntry | ScoreboardTeamEntry;
|
|
29
|
+
|
|
30
|
+
export interface ScoreboardDetails {
|
|
31
|
+
id: number,
|
|
32
|
+
account_url: string,
|
|
33
|
+
name: string,
|
|
34
|
+
score: number,
|
|
35
|
+
bracket_id: null, // TODO
|
|
36
|
+
bracket_name: null,
|
|
37
|
+
solves: {
|
|
38
|
+
challenge_id: number,
|
|
39
|
+
account_id: number,
|
|
40
|
+
team_id: number | null,
|
|
41
|
+
user_id: number,
|
|
42
|
+
value: number,
|
|
43
|
+
date: string // ISO
|
|
44
|
+
}[]
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: number,
|
|
3
|
+
oauth_id: number | null,
|
|
4
|
+
name: string,
|
|
5
|
+
// password: string,
|
|
6
|
+
email?: string,
|
|
7
|
+
// type: string,
|
|
8
|
+
// secret: string,
|
|
9
|
+
website: string | null,
|
|
10
|
+
affiliation: string | null,
|
|
11
|
+
country: string | null,
|
|
12
|
+
bracket_id: number | null,
|
|
13
|
+
// hidden: boolean,
|
|
14
|
+
// banned: boolean,
|
|
15
|
+
// verified: boolean,
|
|
16
|
+
language: string | null,
|
|
17
|
+
// change_password: boolean,
|
|
18
|
+
team_id: number | null,
|
|
19
|
+
created: string, // ISO
|
|
20
|
+
|
|
21
|
+
fields: [], // TODO
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Award {
|
|
25
|
+
description: string | null,
|
|
26
|
+
date: string,
|
|
27
|
+
id: number,
|
|
28
|
+
category: string,
|
|
29
|
+
user_id: number,
|
|
30
|
+
team_id: null, // TODO
|
|
31
|
+
name: string,
|
|
32
|
+
user: number,
|
|
33
|
+
team: null, // TODO
|
|
34
|
+
value: number,
|
|
35
|
+
icon: string,
|
|
36
|
+
}
|
package/tests/demo.ts
CHANGED
|
@@ -8,16 +8,25 @@ import { CTFdClient } from '../src/client';
|
|
|
8
8
|
password: 'password',
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const challs = await client.
|
|
11
|
+
const challs = await client.challenges.get();
|
|
12
12
|
console.log(challs);
|
|
13
13
|
|
|
14
|
-
const scoreboard = await client.
|
|
14
|
+
const scoreboard = await client.scoreboard.get();
|
|
15
15
|
console.log(scoreboard.slice(0, 5));
|
|
16
16
|
|
|
17
17
|
const chall = challs.find((c) => c.name === 'The Lost Park')!;
|
|
18
|
-
const details = await client.
|
|
18
|
+
const details = await client.challenges.getDetails(chall.id);
|
|
19
19
|
console.log(details);
|
|
20
20
|
|
|
21
|
-
const res = await client.submitFlag(chall.id, 'Major Mark Park');
|
|
21
|
+
const res = await client.challenges.submitFlag(chall.id, 'Major Mark Park');
|
|
22
22
|
console.log(res);
|
|
23
|
+
|
|
24
|
+
const solves = await client.challenges.getSolves(chall.id);
|
|
25
|
+
console.log(solves);
|
|
26
|
+
|
|
27
|
+
const user = await client.users.me.get();
|
|
28
|
+
console.log(user);
|
|
29
|
+
|
|
30
|
+
// This should be equivalent to the above
|
|
31
|
+
console.log(await client.users.getById(user.id));
|
|
23
32
|
})()
|
package/tests/rate-limit.ts
CHANGED
|
@@ -8,12 +8,12 @@ import { CTFdClient } from '../src/client';
|
|
|
8
8
|
password: 'password',
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const chall = (await client.
|
|
11
|
+
const chall = (await client.challenges.get()).find((c) => c.name === 'The Lost Park')!;
|
|
12
12
|
|
|
13
|
-
const res = await client.submitFlag(chall.id, 'test');
|
|
13
|
+
const res = await client.challenges.submitFlag(chall.id, 'test');
|
|
14
14
|
console.log(res);
|
|
15
15
|
|
|
16
16
|
for (let i = 0; i < 20; i++) {
|
|
17
|
-
console.log(await client.submitFlag(chall.id, 'test'));
|
|
17
|
+
console.log(await client.challenges.submitFlag(chall.id, 'test'));
|
|
18
18
|
}
|
|
19
19
|
})()
|