Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions packages/api-types/src/responses/goodUserData.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ data:
- solves
- id
- createdAt
perms:
type: integer
required:
- name
- division
Expand Down
2 changes: 2 additions & 0 deletions packages/api-types/src/responses/goodUserSelfData.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ data:
type: array
items:
type: string
perms:
type: integer
required:
- id
- name
Expand Down
3 changes: 1 addition & 2 deletions packages/client/src/api/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const login = async ({ teamToken, ctftimeToken }) => {

export const logout = () => {
localStorage.removeItem('token')

localStorage.removeItem('userPerms')
return route('/')
}

Expand Down Expand Up @@ -74,7 +74,6 @@ export const register = async ({
switch (resp.kind) {
case 'goodRegister':
localStorage.setItem('token', resp.data.authToken)

return route('/profile')
case 'goodVerifySent':
return {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/api/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { request, handleResponse } from './util'

export const privateProfile = async () => {
const resp = await request('GET', '/users/me')

localStorage.setItem('userPerms', resp?.data?.perms || 0)
return handleResponse({ resp, valid: ['goodUserSelfData'] })
}

Expand All @@ -14,7 +14,7 @@ export const pendingPrivateProfile = async ({ authToken }) => {
},
})
).json()

localStorage.setItem('userPerms', data.perms || 0)
return data
}

Expand Down
10 changes: 7 additions & 3 deletions packages/client/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import AdminChallenges from './routes/admin/challs'
import { ToastProvider } from './components/toast'

import { navigateRef } from './history-hack'
import { hasChallsReadPermission } from './util/permissions'

const LoggedOutRedir = <Navigate to='/' />
const LoggedInRedir = <Navigate to='/profile' />
Expand Down Expand Up @@ -61,11 +62,14 @@ function App({ classes }) {
path: '/challs',
name: 'Challenges',
},
{
]
// Check if the user has admin permissions
if (hasChallsReadPermission()) {
loggedInPaths.push({
element: <AdminChallenges />,
path: '/admin/challs',
},
]
})
}

const allPaths = [
{
Expand Down
32 changes: 23 additions & 9 deletions packages/client/src/components/admin/problem.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import { useToast } from '../../components/toast'
import { encodeFile } from '../../util'

import { hasChallsWritePermission } from '../../util/permissions'

const DeleteModal = withStyles(
{
modalBody: {
Expand Down Expand Up @@ -179,6 +181,13 @@ const Problem = ({ classes, problem, update: updateClient }) => {

const handleUpdate = async e => {
e.preventDefault()
if (!hasChallsWritePermission()) {
toast({
body: 'You do not have permission to update challenges',
type: 'error',
})
return
}

const data = await updateChallenge({
id: problem.id,
Expand Down Expand Up @@ -225,6 +234,19 @@ const Problem = ({ classes, problem, update: updateClient }) => {
action()
}, [problem, toast, closeDeleteModal])

const ProblemActions = hasChallsWritePermission() ? (
<Fragment>
<button className='btn-small btn-info'>Update</button>
<button
className='btn-small btn-danger'
onClick={openDeleteModal}
type='button'
>
Delete
</button>
</Fragment>
) : null

return (
<Fragment>
<div className={`frame ${classes.frame}`}>
Expand Down Expand Up @@ -350,16 +372,8 @@ const Problem = ({ classes, problem, update: updateClient }) => {
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>
{ProblemActions}
</div>
</form>
</div>
Expand Down
19 changes: 15 additions & 4 deletions packages/client/src/routes/admin/challs.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import withStyles from '../../components/jss'
import Problem from '../../components/admin/problem'

import { getChallenges } from '../../api/admin/challs'
import { hasChallsWritePermission } from '../../util/permissions'

const SAMPLE_PROBLEM = {
name: '',
Expand All @@ -26,10 +27,15 @@ const Challenges = ({ classes }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const newId = useMemo(() => uuid(), [problems])

const completeProblems = problems.concat({
...SAMPLE_PROBLEM,
id: newId,
})
// Check if the user has write permissions for challenges
// if not, don't show the sample problem
const hasChallsWritePerm = hasChallsWritePermission()
const completeProblems = hasChallsWritePerm
? problems.concat({
...SAMPLE_PROBLEM,
id: newId,
})
: problems

useEffect(() => {
document.title = `Admin Challenges | ${config.ctfName}`
Expand Down Expand Up @@ -69,6 +75,11 @@ const Challenges = ({ classes }) => {
return (
<div className={`row ${classes.row}`}>
<div className='col-9'>
{completeProblems.length === 0 && (
<div className='alert alert-info' style={{ textAlign: 'center' }}>
<p>No challenges found</p>
</div>
)}
{completeProblems.map(problem => {
return (
<Problem
Expand Down
15 changes: 15 additions & 0 deletions packages/client/src/util/permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const hasPermission = (userPerms, requiredPerms) => {
return (userPerms & requiredPerms) === requiredPerms
}

export const hasChallsReadPermission = () => {
return getStoredPermissions() > 0
}

export const hasChallsWritePermission = () => {
return getStoredPermissions() > 1
}

export const getStoredPermissions = () => {
return parseInt(localStorage.getItem(`userPerms`) || '0', 0)
}
1 change: 1 addition & 0 deletions packages/server/src/api/users/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export default makeFastifyRoute(usersMeGet, async ({ user, res }) => {
allowedDivisions,
id: uuid,
email: user.email,
perms: user.perms,
})
})