diff --git a/packages/api-types/src/responses/goodUserData.yml b/packages/api-types/src/responses/goodUserData.yml index 9bda709..0a40b1c 100644 --- a/packages/api-types/src/responses/goodUserData.yml +++ b/packages/api-types/src/responses/goodUserData.yml @@ -47,6 +47,8 @@ data: - solves - id - createdAt + perms: + type: integer required: - name - division diff --git a/packages/api-types/src/responses/goodUserSelfData.yml b/packages/api-types/src/responses/goodUserSelfData.yml index f56eede..d1c2e27 100644 --- a/packages/api-types/src/responses/goodUserSelfData.yml +++ b/packages/api-types/src/responses/goodUserSelfData.yml @@ -57,6 +57,8 @@ data: type: array items: type: string + perms: + type: integer required: - id - name diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js index abd3501..3b3a78e 100644 --- a/packages/client/src/api/auth.js +++ b/packages/client/src/api/auth.js @@ -34,7 +34,7 @@ export const login = async ({ teamToken, ctftimeToken }) => { export const logout = () => { localStorage.removeItem('token') - + localStorage.removeItem('userPerms') return route('/') } @@ -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 { diff --git a/packages/client/src/api/profile.js b/packages/client/src/api/profile.js index 0607f62..56abe56 100644 --- a/packages/client/src/api/profile.js +++ b/packages/client/src/api/profile.js @@ -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'] }) } @@ -14,7 +14,7 @@ export const pendingPrivateProfile = async ({ authToken }) => { }, }) ).json() - + localStorage.setItem('userPerms', data.perms || 0) return data } diff --git a/packages/client/src/app.js b/packages/client/src/app.js index 585573e..d599109 100644 --- a/packages/client/src/app.js +++ b/packages/client/src/app.js @@ -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 = const LoggedInRedir = @@ -61,11 +62,14 @@ function App({ classes }) { path: '/challs', name: 'Challenges', }, - { + ] + // Check if the user has admin permissions + if (hasChallsReadPermission()) { + loggedInPaths.push({ element: , path: '/admin/challs', - }, - ] + }) + } const allPaths = [ { diff --git a/packages/client/src/components/admin/problem.js b/packages/client/src/components/admin/problem.js index 969c0c5..0e480bb 100644 --- a/packages/client/src/components/admin/problem.js +++ b/packages/client/src/components/admin/problem.js @@ -11,6 +11,8 @@ import { import { useToast } from '../../components/toast' import { encodeFile } from '../../util' +import { hasChallsWritePermission } from '../../util/permissions' + const DeleteModal = withStyles( { modalBody: { @@ -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, @@ -225,6 +234,19 @@ const Problem = ({ classes, problem, update: updateClient }) => { action() }, [problem, toast, closeDeleteModal]) + const ProblemActions = hasChallsWritePermission() ? ( + + + + + ) : null + return (
@@ -350,16 +372,8 @@ const Problem = ({ classes, problem, update: updateClient }) => { onChange={handleFileUpload} />
-
- - + {ProblemActions}
diff --git a/packages/client/src/routes/admin/challs.js b/packages/client/src/routes/admin/challs.js index 99f5d21..9f6964f 100644 --- a/packages/client/src/routes/admin/challs.js +++ b/packages/client/src/routes/admin/challs.js @@ -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: '', @@ -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}` @@ -69,6 +75,11 @@ const Challenges = ({ classes }) => { return (
+ {completeProblems.length === 0 && ( +
+

No challenges found

+
+ )} {completeProblems.map(problem => { return ( { + 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) +} diff --git a/packages/server/src/api/users/me.ts b/packages/server/src/api/users/me.ts index 066cbd5..0760699 100644 --- a/packages/server/src/api/users/me.ts +++ b/packages/server/src/api/users/me.ts @@ -18,5 +18,6 @@ export default makeFastifyRoute(usersMeGet, async ({ user, res }) => { allowedDivisions, id: uuid, email: user.email, + perms: user.perms, }) })