Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c9cc9e4
e2e with playwright
meumar-osec Mar 24, 2025
232fec0
feat: e2e
meumar-osec Mar 26, 2025
d51aaa4
Merge branch 'main' of https://github.com/otter-sec/rctf into feat/e2e
meumar-osec Mar 28, 2025
c553493
Merge branch 'main' of https://github.com/otter-sec/rctf into feat/e2e
meumar-osec Mar 28, 2025
730b9c1
add: tests for admin challenges
meumar-osec Apr 2, 2025
c40f716
update: test config file added
meumar-osec Apr 2, 2025
0925315
admin page access control
meumar-osec Apr 7, 2025
b158a79
admin page UI updates & access control issue fix
meumar-osec Apr 10, 2025
5597fbb
revert admin access check
meumar-osec Oct 29, 2025
99b11af
chore: update to node 22 and yarn v4
trixter-osec Jul 15, 2025
32bcf55
feat(docs): clarify smtp, gcs upload provider, and ctftime sections
trixter-osec Jul 16, 2025
b0db294
feat(ci): switch to ghcr
trixter-osec Jul 16, 2025
abfa0e1
fix(ci): run on main
trixter-osec Jul 17, 2025
bdc06db
refactor(ci): update deprecated actions
trixter-osec Jul 17, 2025
a3d0ab5
chore: change URLs to use osec.io
trixter-osec Jul 17, 2025
f42e739
fix(install): use the correct config directory and docker image
trixter-osec Jul 17, 2025
438bace
chore(docs): update README to osec
trixter-osec Jul 17, 2025
8e79a6e
chore: update yarn.lock with playwright dependencies
meumar-osec Oct 29, 2025
4175f8e
Merge branch 'main' into feat/e2e
meumar-osec Oct 29, 2025
bcdcaf8
fix: code format
meumar-osec Oct 29, 2025
f7e3f3e
Merge branch 'main' into feat/e2e
meumar-osec Oct 31, 2025
d1abbd3
fix: resolved PR comments
meumar-osec Oct 31, 2025
8d1f171
fix: eslint fixes
es3n1n Nov 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ node_modules

dist
/.env
/.env.*
/data
/rctf.d/*
!/rctf.d/.keep
Expand All @@ -39,3 +40,5 @@ $RECYCLE.BIN

# coverage
coverage

test-results
8 changes: 8 additions & 0 deletions packages/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
*.env
2 changes: 2 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@babel/preset-react": "7.16.0",
"@babel/preset-typescript": "7.16.0",
"@emotion/jest": "11.5.0",
"@playwright/test": "1.50.1",
"@prefresh/babel-plugin": "0.4.1",
"@prefresh/webpack": "3.3.2",
"@reach/auto-id": "0.16.0",
Expand All @@ -39,6 +40,7 @@
"@testing-library/preact": "2.0.1",
"@testing-library/user-event": "13.5.0",
"@theme-ui/preset-base": "0.9.1",
"@types/node": "16.11.6",
"@types/react": "17.0.33",
"@types/webpack-env": "1.16.3",
"babel-loader": "8.2.3",
Expand Down
30 changes: 30 additions & 0 deletions packages/client/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
Comment thread
meumar-osec marked this conversation as resolved.
Outdated
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
trace: 'on-first-retry',

baseURL: 'http://localhost:8080',
headless: true,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
},
projects: [
Comment thread
meumar-osec marked this conversation as resolved.
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],

timeout: 30000,
})
263 changes: 140 additions & 123 deletions packages/client/src/components/admin/problem.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,137 +229,154 @@ const Problem = ({ classes, problem, update: updateClient }) => {
<Fragment>
<div className={`frame ${classes.frame}`}>
<div className='frame__body'>
<form onSubmit={handleUpdate}>
<div className='row u-no-padding'>
<div className={`col-6 ${classes.header}`}>
<input
autoComplete='off'
autoCorrect='off'
required
className='form-group-input input-small'
placeholder='Category'
value={category}
onChange={handleCategoryChange}
/>
<input
autoComplete='off'
autoCorrect='off'
required
className='form-group-input input-small'
placeholder='Problem Name'
value={name}
onChange={handleNameChange}
/>
<div className='form-ext-control form-ext-checkbox'>
<input
id={`chall-${problem.id}-tiebreak-eligible`}
type='checkbox'
className='form-ext-input'
checked={tiebreakEligible}
onChange={handleTiebreakEligibleChange}
<form onSubmit={handleUpdate} style={{ marginTop: '1em' }}>
<div
className={`frame ${classes.frame}`}
style={{ boxShadow: 'none' }}
>
<div className='frame__body'>
{/* Section: Basic Info */}
<div className='row u-no-padding'>
<div className='col-6'>
<label>Category</label>
<input
className='form-group-input input-small'
placeholder='Category'
value={category}
onChange={handleCategoryChange}
required
/>
<label>Problem Name</label>
<input
className='form-group-input input-small'
placeholder='Problem Name'
value={name}
onChange={handleNameChange}
required
/>
<label>Author</label>
<input
className='form-group-input input-small'
placeholder='Author'
value={author}
onChange={handleAuthorChange}
required
/>
</div>

<div className='col-6'>
<label>Minimum Points</label>
<input
type='number'
className='form-group-input input-small'
value={minPoints}
onChange={handleMinPointsChange}
required
/>
<label>Maximum Points</label>
<input
type='number'
className='form-group-input input-small'
value={maxPoints}
onChange={handleMaxPointsChange}
required
/>
<div
className='form-ext-control form-ext-checkbox'
style={{ marginTop: '1em' }}
>
<input
id={`chall-${problem.id}-tiebreak-eligible`}
type='checkbox'
className='form-ext-input'
checked={tiebreakEligible}
onChange={handleTiebreakEligibleChange}
/>
<label
htmlFor={`chall-${problem.id}-tiebreak-eligible`}
className='form-ext-label'
>
Eligible for tiebreaks?
</label>
</div>
</div>
</div>

{/* Section: Description */}
<div className='input-control' style={{ marginTop: '1em' }}>
<label>Description</label>
<textarea
placeholder='Describe the problem'
value={description}
onChange={handleDescriptionChange}
required
className='form-group-input input-small'
/>
<label
htmlFor={`chall-${problem.id}-tiebreak-eligible`}
className='form-ext-label'
>
Eligible for tiebreaks?
</label>
</div>
</div>
<div className={`col-6 ${classes.header}`}>
<input
autoComplete='off'
autoCorrect='off'
required
className='form-group-input input-small'
placeholder='Author'
value={author}
onChange={handleAuthorChange}
/>
<input
className='form-group-input input-small'
type='number'
required
value={minPoints}
onChange={handleMinPointsChange}
/>
<input
className='form-group-input input-small'
type='number'
required
value={maxPoints}
onChange={handleMaxPointsChange}
/>
</div>
</div>

<div className='content-no-padding u-center'>
<div className={`divider ${classes.divider}`} />
</div>
{/* Section: Flag */}
<div className='input-control'>
<label>Flag</label>
<input
placeholder='Flag'
value={flag}
onChange={handleFlagChange}
className='form-group-input input-small'
required
/>
</div>

<textarea
autoComplete='off'
autoCorrect='off'
placeholder='Description'
value={description}
onChange={handleDescriptionChange}
/>
<div className='input-control'>
<input
autoComplete='off'
autoCorrect='off'
required
className='form-group-input input-small'
placeholder='Flag'
value={flag}
onChange={handleFlagChange}
/>
</div>
{/* Section: Files */}
<div style={{ marginTop: '1em' }}>
<label>Challenge Files</label>
<input
type='file'
multiple
onChange={handleFileUpload}
className='form-group-input input-small'
/>
{problem.files.length > 0 && (
<div>
<p
className={`frame__subtitle u-no-margin ${classes.downloadsHeader}`}
>
Existing Files
</p>
<div className='tag-container'>
{problem.files.map(file => (
<div className={`tag ${classes.tag}`} key={file.url}>
<a download href={file.url}>
{file.name}
</a>
<div
className='tag tag--delete'
style='margin: 0; margin-left: 3px'
onClick={handleRemoveFile(file)}
/>
</div>
))}
</div>
</div>
)}
</div>

{problem.files.length !== 0 && (
<div>
<p
className={`frame__subtitle u-no-margin ${classes.downloadsHeader}`}
{/* Section: Actions */}
<div
className={`form-section ${classes.controls}`}
style={{ marginTop: '1.5em' }}
>
Downloads
</p>
<div className='tag-container'>
{problem.files.map(file => {
return (
<div className={`tag ${classes.tag}`} key={file.url}>
<a download href={file.url}>
{file.name}
</a>
<div
className='tag tag--delete'
style='margin: 0; margin-left: 3px'
onClick={handleRemoveFile(file)}
/>
</div>
)
})}
<button type='submit' className='btn-small btn-info'>
Update Challenge
</button>
<button
type='button'
className='btn-small btn-danger'
onClick={openDeleteModal}
>
Delete Challenge
</button>
</div>
</div>
)}

<div className='input-control'>
<input
className='form-group-input input-small'
type='file'
multiple
onChange={handleFileUpload}
/>
</div>

<div className={`form-section ${classes.controls}`}>
<button className='btn-small btn-info'>Update</button>
<button
className='btn-small btn-danger'
onClick={openDeleteModal}
type='button'
>
Delete
</button>
</div>
</form>
</div>
Expand Down
31 changes: 31 additions & 0 deletions packages/client/testConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import dotenv from 'dotenv'

dotenv.config({ path: '.env.test' })
Comment thread
meumar-osec marked this conversation as resolved.

const testConfig = {
baseUrl: process.env.BASE_URL || 'http://localhost:8080',
ctfName: process.env.NAME,
homeContent: process.env.HOME_CONTENT,
loginToken: process.env.LOGIN_TOKEN,
divisions: process.env.DIVISIONS ? JSON.parse(process.env.DIVISIONS) : {},

testChal: process.env.TEST_CHAL,
testChalAns: process.env.TEST_CHAL_ANSWER || '',

testRegEmail: process.env.TEST_REG_EMAIL || '',
testRegName: process.env.TEST_REG_NAME || '',

testUpdateEmail: process.env.TEST_UPDATE_EMAIL || '',
testUpdateName: process.env.TEST_UPDATE_NAME || '',

testNewEmail: process.env.TEST_NEW_MEMBER || '',

testNewChalName: process.env.TEST_NEW_CHAL_NAME || '',
testNewChalAuthor: process.env.TEST_NEW_CHAL_AUTHOR || '',
testNewChalDes: process.env.TEST_NEW_CHAL_DES || '',
testNewChalFlag: process.env.TEST_NEW_CHAL_FLAG || '',

regularUserToken: process.env.REGULAR_USER_TOKEN || '',
}

export default testConfig
Loading
Loading