Skip to content

Commit ca173cd

Browse files
committed
add instancer support
1 parent 90f1fdf commit ca173cd

File tree

7 files changed

+74
-38
lines changed

7 files changed

+74
-38
lines changed

client/src/api/challenges.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { request, handleResponse } from './util'
2-
import { privateProfile } from './profile'
32

43
export const getChallenges = async () => {
54
const resp = await request('GET', '/challs')
@@ -13,20 +12,6 @@ export const getChallenges = async () => {
1312
return handleResponse({ resp, valid: ['goodChallenges'] })
1413
}
1514

16-
export const getPrivateSolves = async () => {
17-
const { data, error } = await privateProfile()
18-
19-
if (error) {
20-
return { error }
21-
}
22-
return {
23-
solves: data.solves,
24-
// LA CTF: track bloods
25-
bloods: data.bloods
26-
// --------------------
27-
}
28-
}
29-
3015
export const getSolves = ({ challId, limit, offset }) => {
3116
return request('GET', `/challs/${encodeURIComponent(challId)}/solves`, {
3217
limit,

client/src/routes/challs.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import Problem from '../components/problem'
66
import NotStarted from '../components/not-started'
77
import { useToast } from '../components/toast'
88

9-
import { getChallenges, getPrivateSolves } from '../api/challenges'
9+
import { privateProfile } from '../api/profile'
10+
import { getChallenges } from '../api/challenges'
1011

1112
const loadStates = {
1213
pending: 0,
@@ -84,25 +85,30 @@ const Challenges = ({ classes }) => {
8485
}
8586
})
8687

87-
setProblems(data)
88-
setCategories(newCategories)
89-
}
90-
action()
91-
}, [toast, categories, problems])
92-
93-
useEffect(() => {
94-
const action = async () => {
95-
const { solves, bloods, error } = await getPrivateSolves()
96-
if (error) {
97-
toast({ body: error, type: 'error' })
88+
const { data: profileData, error: profileError } = await privateProfile()
89+
if (profileError) {
90+
toast({ body: profileError, type: 'error' })
9891
return
9992
}
10093

101-
setSolveIDs(solves.map(solve => solve.id))
102-
setBloods(new Map(bloods.map(x => [x.id, x.rank])))
94+
setSolveIDs(profileData.solves.map(solve => solve.id))
95+
setBloods(new Map(profileData.bloods.map(x => [x.id, x.rank])))
96+
97+
const instancerPlaceholderRegex = /\{instancer:([a-zA-Z0-9-]+)\}/g
98+
if (config.instancerUrl !== '') {
99+
data.forEach(problem => {
100+
problem.description = problem.description.replaceAll('{instancer}', `[Deploy challenge](${encodeURI(config.instancerUrl)}/chall/${problem.id}?token=${encodeURIComponent(profileData.instancerToken)})`)
101+
problem.description = problem.description.replaceAll('{instancer_token}', encodeURIComponent(profileData.instancerToken))
102+
problem.description = problem.description.replaceAll('{instancer_url}', encodeURI(config.instancerUrl))
103+
problem.description = problem.description.replaceAll(instancerPlaceholderRegex, `${encodeURI(config.instancerUrl)}/chall/$1?token=${encodeURIComponent(profileData.instancerToken)}`)
104+
})
105+
}
106+
107+
setProblems(data)
108+
setCategories(newCategories)
103109
}
104110
action()
105-
}, [toast])
111+
}, [toast, categories, problems])
106112

107113
useEffect(() => {
108114
localStorage.challPageState = JSON.stringify({ categories, showSolved })

client/src/routes/profile.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,27 @@ const TeamCodeCard = withStyles({
150150
)
151151
})
152152

153+
const InstancerCodeCard = withStyles({
154+
btn: {
155+
marginRight: '10px'
156+
}
157+
}, ({ instancerToken, classes }) => {
158+
const instancerURL = `${config.instancerUrl}/login?token=${encodeURIComponent(instancerToken)}`
159+
160+
return (
161+
<div class='card'>
162+
<div class='content'>
163+
<p>Instancer Login</p>
164+
<p class='font-thin'>Use this link to login to the challenge instancer.</p>
165+
166+
<a href={instancerURL} target='_blank' rel='noreferrer'>
167+
<button class={`${classes.btn} btn-info u-center`} name='btn' value='submit' type='button'>Instancer Login</button>
168+
</a>
169+
</div>
170+
</div>
171+
)
172+
})
173+
153174
const UpdateCard = withStyles({
154175
form: {
155176
'& button': {
@@ -303,6 +324,7 @@ const Profile = ({ uuid, classes }) => {
303324
score,
304325
solves,
305326
teamToken,
327+
instancerToken,
306328
ctftimeId,
307329
allowedDivisions
308330
} = data
@@ -371,6 +393,7 @@ const Profile = ({ uuid, classes }) => {
371393
{isPrivate && (
372394
<div class={classes.privateCol}>
373395
<TeamCodeCard {...{ teamToken }} />
396+
{ config.instancerUrl !== '' && <InstancerCodeCard instancerToken={instancerToken} /> }
374397
<UpdateCard {...{ name, email, divisionId, allowedDivisions, onUpdate: onProfileUpdate }} />
375398
{config.ctftime && (
376399
<CtftimeCard {...{ ctftimeId, onUpdate: onProfileUpdate }} />

server/api/users/me.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@ export default {
1515

1616
const allowedDivisions = util.restrict.allowedDivisions(user.email)
1717

18+
const instancerToken = await auth.token.getToken(auth.token.tokenKinds.instancer, {
19+
teamId: uuid,
20+
email: user.email,
21+
name: user.name
22+
})
23+
1824
return [responses.goodUserData, {
1925
...userData,
2026
teamToken,
27+
instancerToken,
2128
allowedDivisions,
2229
id: uuid,
2330
email: user.email

server/auth/token.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export enum tokenKinds {
1111
auth = 0,
1212
team = 1,
1313
verify = 2,
14-
ctftimeAuth = 4
14+
ctftimeAuth = 4,
15+
instancer = 8
1516
}
1617

1718
export type VerifyTokenKinds = 'update' | 'register' | 'recover'
@@ -20,6 +21,12 @@ export type AuthTokenData = string
2021

2122
export type TeamTokenData = string
2223

24+
export type InstancerAuthTokenData = {
25+
teamId: string
26+
email: User['email']
27+
name: User['name']
28+
}
29+
2330
interface BaseVerifyTokenData {
2431
verifyId: string
2532
kind: VerifyTokenKinds
@@ -59,6 +66,7 @@ type TokenDataTypes = {
5966
[tokenKinds.team]: TeamTokenData;
6067
[tokenKinds.verify]: VerifyTokenData;
6168
[tokenKinds.ctftimeAuth]: CtftimeAuthTokenData;
69+
[tokenKinds.instancer]: InstancerAuthTokenData;
6270
}
6371

6472
export type Token = string
@@ -73,7 +81,8 @@ const tokenExpiries: Record<ValueOf<typeof tokenKinds>, number> = {
7381
[tokenKinds.auth]: Infinity,
7482
[tokenKinds.team]: Infinity,
7583
[tokenKinds.verify]: config.loginTimeout,
76-
[tokenKinds.ctftimeAuth]: config.loginTimeout
84+
[tokenKinds.ctftimeAuth]: config.loginTimeout,
85+
[tokenKinds.instancer]: Infinity
7786
}
7887

7988
const timeNow = () => Math.floor(Date.now() / 1000)

server/config/client.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ export type ClientConfig = Pick<ServerConfig,
1313
'startTime' |
1414
'endTime' |
1515
'userMembers' |
16-
'faviconUrl'
16+
'faviconUrl' |
17+
'instancerUrl'
1718
> & {
1819
emailEnabled: boolean;
19-
ctftime?: Pick<NonNullable<ServerConfig['ctftime']>, 'clientId'>
20-
recaptcha?: Pick<NonNullable<ServerConfig['recaptcha']>, 'siteKey' | 'protectedActions'>
20+
ctftime?: Pick<NonNullable<ServerConfig['ctftime']>, 'clientId'>;
21+
recaptcha?: Pick<NonNullable<ServerConfig['recaptcha']>, 'siteKey' | 'protectedActions'>;
2122
}
2223

2324
const config: ClientConfig = {
@@ -45,7 +46,8 @@ const config: ClientConfig = {
4546
: {
4647
siteKey: server.recaptcha.siteKey,
4748
protectedActions: server.recaptcha.protectedActions
48-
}
49+
},
50+
instancerUrl: server.instancerUrl
4951
}
5052

5153
export default config

server/config/server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export type ServerConfig = {
9696
graphSampleTime: number;
9797
}
9898
loginTimeout: number;
99+
100+
instancerUrl: string;
99101
}
100102

101103
const jsonLoader = (file: string) => JSON.parse(file) as PartialDeep<ServerConfig>
@@ -183,7 +185,8 @@ const envConfig: PartialDeep<ServerConfig> = {
183185
graphMaxTeams: nullsafeParseInt(process.env.RCTF_LEADERBOARD_GRAPH_MAX_TEAMS),
184186
graphSampleTime: nullsafeParseInt(process.env.RCTF_LEADERBOARD_GRAPH_SAMPLE_TIME)
185187
},
186-
loginTimeout: nullsafeParseInt(process.env.RCTF_LOGIN_TIMEOUT)
188+
loginTimeout: nullsafeParseInt(process.env.RCTF_LOGIN_TIMEOUT),
189+
instancerUrl: process.env.RCTF_INSTANCER_URL
187190
}
188191

189192
const defaultConfig: PartialDeep<ServerConfig> = {
@@ -216,7 +219,8 @@ const defaultConfig: PartialDeep<ServerConfig> = {
216219
graphMaxTeams: 10,
217220
graphSampleTime: 1800000
218221
},
219-
loginTimeout: 3600000
222+
loginTimeout: 3600000,
223+
instancerUrl: ''
220224
}
221225

222226
const config = deepMerge.all([defaultConfig, ...fileConfigs, removeUndefined(envConfig)]) as ServerConfig

0 commit comments

Comments
 (0)