From c983f8367c21cbd13c4c4ca374544a5c7bd17779 Mon Sep 17 00:00:00 2001 From: Umar Sheik Date: Wed, 29 Oct 2025 14:56:10 +0530 Subject: [PATCH 1/2] fix: restrict admin routes access --- .../api-types/src/responses/goodLogin.yml | 4 +- .../api-types/src/responses/goodRegister.yml | 4 +- .../api-types/src/responses/goodVerify.yml | 4 +- packages/client/src/api/auth.js | 8 +++- packages/client/src/app.js | 10 +++-- .../client/src/components/pending-token.js | 6 +-- packages/client/src/routes/login.js | 4 +- packages/client/src/routes/register.js | 2 +- packages/client/src/routes/verify.js | 4 +- packages/client/src/util/permissions.js | 11 ++++++ packages/server/src/api/auth/login.ts | 2 +- packages/server/src/api/auth/verify.js | 2 +- packages/server/src/auth/register.ts | 2 +- packages/server/test/integration/auth.js | 37 +++++++++++++++++++ 14 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 packages/client/src/util/permissions.js diff --git a/packages/api-types/src/responses/goodLogin.yml b/packages/api-types/src/responses/goodLogin.yml index 184d27f..79509e7 100644 --- a/packages/api-types/src/responses/goodLogin.yml +++ b/packages/api-types/src/responses/goodLogin.yml @@ -5,4 +5,6 @@ data: properties: authToken: type: string - required: ['authToken'] + perms: + type: integer + required: ['authToken', 'perms'] diff --git a/packages/api-types/src/responses/goodRegister.yml b/packages/api-types/src/responses/goodRegister.yml index d68fb32..5e3a314 100644 --- a/packages/api-types/src/responses/goodRegister.yml +++ b/packages/api-types/src/responses/goodRegister.yml @@ -5,4 +5,6 @@ data: properties: authToken: type: string - required: ['authToken'] + perms: + type: integer + required: ['authToken', 'perms'] diff --git a/packages/api-types/src/responses/goodVerify.yml b/packages/api-types/src/responses/goodVerify.yml index 6849263..dcc1658 100644 --- a/packages/api-types/src/responses/goodVerify.yml +++ b/packages/api-types/src/responses/goodVerify.yml @@ -5,4 +5,6 @@ data: properties: authToken: type: string - required: ['authToken'] + perms: + type: integer + required: ['authToken', 'perms'] diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js index abd3501..47e399d 100644 --- a/packages/client/src/api/auth.js +++ b/packages/client/src/api/auth.js @@ -1,8 +1,10 @@ import { request } from './util' import { route } from '../history-hack' -export const setAuthToken = ({ authToken }) => { +export const setAuthToken = ({ authToken, perms }) => { localStorage.token = authToken + // Set the user permissions in local storage + localStorage.userPerms = perms route('/profile') } @@ -15,6 +17,7 @@ export const login = async ({ teamToken, ctftimeToken }) => { case 'goodLogin': return { authToken: resp.data.authToken, + perms: resp.data.perms, } case 'badTokenVerification': return { @@ -34,6 +37,7 @@ export const login = async ({ teamToken, ctftimeToken }) => { export const logout = () => { localStorage.removeItem('token') + localStorage.removeItem('userPerms') return route('/') } @@ -47,6 +51,7 @@ export const verify = async ({ verifyToken }) => { case 'goodVerify': return { authToken: resp.data.authToken, + perms: resp.data.perms, } case 'goodEmailSet': return { @@ -74,6 +79,7 @@ export const register = async ({ switch (resp.kind) { case 'goodRegister': localStorage.setItem('token', resp.data.authToken) + localStorage.setItem('userPerms', resp.data.perms || 0) return route('/profile') case 'goodVerifySent': diff --git a/packages/client/src/app.js b/packages/client/src/app.js index 585573e..025199d 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 { getStoredPermissions, isAdmin } 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 (isAdmin(getStoredPermissions())) { + loggedInPaths.push({ element: , path: '/admin/challs', - }, - ] + }) + } const allPaths = [ { diff --git a/packages/client/src/components/pending-token.js b/packages/client/src/components/pending-token.js index 0c172d7..c746fe4 100644 --- a/packages/client/src/components/pending-token.js +++ b/packages/client/src/components/pending-token.js @@ -3,7 +3,7 @@ import { useEffect, useState, useCallback } from 'preact/hooks' import { setAuthToken } from '../api/auth' import { pendingPrivateProfile } from '../api/profile' -const PendingToken = ({ authToken }) => { +const PendingToken = ({ authToken, perms }) => { const [user, setUser] = useState(null) useEffect(() => { ;(async () => { @@ -15,8 +15,8 @@ const PendingToken = ({ authToken }) => { })() }, [authToken]) const handleLoginClick = useCallback(() => { - setAuthToken({ authToken }) - }, [authToken]) + setAuthToken({ authToken, perms }) + }, [authToken, perms]) if (!user) { return null } diff --git a/packages/client/src/routes/login.js b/packages/client/src/routes/login.js index adc0f4a..56c0b94 100644 --- a/packages/client/src/routes/login.js +++ b/packages/client/src/routes/login.js @@ -142,7 +142,7 @@ export default withStyles( }) const loginRes = await login({ ctftimeToken }) if (loginRes.authToken) { - setAuthToken({ authToken: loginRes.authToken }) + setAuthToken({ authToken: loginRes.authToken, perms: loginRes.perms }) } if (loginRes && loginRes.badUnknownUser) { this.setState({ @@ -175,7 +175,7 @@ export default withStyles( teamToken, }) if (result.authToken) { - setAuthToken({ authToken: result.authToken }) + setAuthToken({ authToken: result.authToken, perms: result.perms }) return } this.setState({ diff --git a/packages/client/src/routes/register.js b/packages/client/src/routes/register.js index 718e2d4..9e9e2e8 100644 --- a/packages/client/src/routes/register.js +++ b/packages/client/src/routes/register.js @@ -154,7 +154,7 @@ export default withStyles( ctftimeToken, }) if (loginRes.authToken) { - setAuthToken({ authToken: loginRes.authToken }) + setAuthToken({ authToken: loginRes.authToken, perms: loginRes.perms }) } if (loginRes.badUnknownUser) { this.setState({ diff --git a/packages/client/src/routes/verify.js b/packages/client/src/routes/verify.js index b00d57b..05615b6 100644 --- a/packages/client/src/routes/verify.js +++ b/packages/client/src/routes/verify.js @@ -7,6 +7,7 @@ import { Fragment } from 'preact' const Verify = () => { const [authToken, setAuthToken] = useState(null) + const [perms, setPerms] = useState(0) const [emailSet, setEmailSet] = useState(false) const [error, setError] = useState(null) @@ -20,6 +21,7 @@ const Verify = () => { const verifyRes = await verify({ verifyToken: qs.get('token') }) if (verifyRes.authToken) { setAuthToken(verifyRes.authToken) + setPerms(verifyRes.perms) } else if (verifyRes.emailSet) { setEmailSet(true) } else { @@ -54,7 +56,7 @@ const Verify = () => { ) } - return + return } export default Verify diff --git a/packages/client/src/util/permissions.js b/packages/client/src/util/permissions.js new file mode 100644 index 0000000..7988374 --- /dev/null +++ b/packages/client/src/util/permissions.js @@ -0,0 +1,11 @@ +export const hasPermission = (userPerms, requiredPerms) => { + return (userPerms & requiredPerms) === requiredPerms +} + +export const isAdmin = userPerms => { + return userPerms > 0 +} + +export const getStoredPermissions = () => { + return parseInt(localStorage.getItem(`userPerms`) || '0', 0) +} diff --git a/packages/server/src/api/auth/login.ts b/packages/server/src/api/auth/login.ts index 80c38a5..0adf01c 100644 --- a/packages/server/src/api/auth/login.ts +++ b/packages/server/src/api/auth/login.ts @@ -29,6 +29,6 @@ export default makeFastifyRoute( return res.badUnknownUser() } const authToken = await getToken(tokenKinds.auth, user.id) - return res.goodLogin({ authToken }) + return res.goodLogin({ authToken, perms: user.perms }) } ) diff --git a/packages/server/src/api/auth/verify.js b/packages/server/src/api/auth/verify.js index fb28a25..bf713ad 100644 --- a/packages/server/src/api/auth/verify.js +++ b/packages/server/src/api/auth/verify.js @@ -39,7 +39,7 @@ export default makeFastifyRoute(authVerifyPost, async ({ req, res }) => { auth.token.tokenKinds.auth, user.id ) - return res.goodVerify({ authToken }) + return res.goodVerify({ authToken, perms: user.perms }) } else if (tokenData.kind === 'update') { let result try { diff --git a/packages/server/src/auth/register.ts b/packages/server/src/auth/register.ts index 6af4ac6..cccbc46 100644 --- a/packages/server/src/auth/register.ts +++ b/packages/server/src/auth/register.ts @@ -47,5 +47,5 @@ export const register = async ( throw e } const authToken = await getToken(tokenKinds.auth, userUuid) - return res.goodRegister({ authToken }) + return res.goodRegister({ authToken, perms: 0 }) } diff --git a/packages/server/test/integration/auth.js b/packages/server/test/integration/auth.js index af80b48..3a640b3 100644 --- a/packages/server/test/integration/auth.js +++ b/packages/server/test/integration/auth.js @@ -10,6 +10,7 @@ import { goodRegister, goodToken, goodUserUpdate, + goodLogin, } from '@rctf/api-types/responses' import * as auth from '../../src/auth' @@ -139,3 +140,39 @@ test('succeeds with goodUserUpdate', async () => { expect(respUser.email).toBe(testUser.email) expect(respUser.division).toBe(nextUser.division) }) + +test('register returns perms field', async () => { + config.email = null + + const newTestUser = generateTestUser() + const resp = await request(app.server) + .post(process.env.API_ENDPOINT + '/auth/register') + .send(newTestUser) + .expect(goodRegister.status) + + expect(resp.body.kind).toBe('goodRegister') + expect(resp.body.data.perms).toBe(0) + expect(typeof resp.body.data.authToken === 'string').toBe(true) +}) + +test('login returns perms field', async () => { + config.email = null + + const user = await database.users.getUserByEmail({ + email: testUser.email, + }) + + const teamToken = await auth.token.getToken( + auth.token.tokenKinds.team, + user.id + ) + + const resp = await request(app.server) + .post(process.env.API_ENDPOINT + '/auth/login') + .send({ teamToken }) + .expect(goodLogin.status) + + expect(resp.body.kind).toBe('goodLogin') + expect(resp.body.data.perms).toBe(user.perms) + expect(typeof resp.body.data.authToken === 'string').toBe(true) +}) From 757512997d000f78d5c8dee3ab4843e0160e09b6 Mon Sep 17 00:00:00 2001 From: Umar Sheik Date: Thu, 30 Oct 2025 20:42:06 +0530 Subject: [PATCH 2/2] fix: PRs comments and better UX --- .../api-types/src/responses/goodLogin.yml | 4 +- .../api-types/src/responses/goodRegister.yml | 4 +- .../api-types/src/responses/goodUserData.yml | 2 + .../src/responses/goodUserSelfData.yml | 2 + .../api-types/src/responses/goodVerify.yml | 4 +- packages/client/src/api/auth.js | 9 +---- packages/client/src/api/profile.js | 4 +- packages/client/src/app.js | 4 +- .../client/src/components/admin/problem.js | 32 +++++++++++----- .../client/src/components/pending-token.js | 6 +-- packages/client/src/routes/admin/challs.js | 19 ++++++++-- packages/client/src/routes/login.js | 4 +- packages/client/src/routes/register.js | 2 +- packages/client/src/routes/verify.js | 4 +- packages/client/src/util/permissions.js | 8 +++- packages/server/src/api/auth/login.ts | 2 +- packages/server/src/api/auth/verify.js | 2 +- packages/server/src/api/users/me.ts | 1 + packages/server/src/auth/register.ts | 2 +- packages/server/test/integration/auth.js | 37 ------------------- 20 files changed, 67 insertions(+), 85 deletions(-) diff --git a/packages/api-types/src/responses/goodLogin.yml b/packages/api-types/src/responses/goodLogin.yml index 79509e7..184d27f 100644 --- a/packages/api-types/src/responses/goodLogin.yml +++ b/packages/api-types/src/responses/goodLogin.yml @@ -5,6 +5,4 @@ data: properties: authToken: type: string - perms: - type: integer - required: ['authToken', 'perms'] + required: ['authToken'] diff --git a/packages/api-types/src/responses/goodRegister.yml b/packages/api-types/src/responses/goodRegister.yml index 5e3a314..d68fb32 100644 --- a/packages/api-types/src/responses/goodRegister.yml +++ b/packages/api-types/src/responses/goodRegister.yml @@ -5,6 +5,4 @@ data: properties: authToken: type: string - perms: - type: integer - required: ['authToken', 'perms'] + required: ['authToken'] 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/api-types/src/responses/goodVerify.yml b/packages/api-types/src/responses/goodVerify.yml index dcc1658..6849263 100644 --- a/packages/api-types/src/responses/goodVerify.yml +++ b/packages/api-types/src/responses/goodVerify.yml @@ -5,6 +5,4 @@ data: properties: authToken: type: string - perms: - type: integer - required: ['authToken', 'perms'] + required: ['authToken'] diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js index 47e399d..3b3a78e 100644 --- a/packages/client/src/api/auth.js +++ b/packages/client/src/api/auth.js @@ -1,10 +1,8 @@ import { request } from './util' import { route } from '../history-hack' -export const setAuthToken = ({ authToken, perms }) => { +export const setAuthToken = ({ authToken }) => { localStorage.token = authToken - // Set the user permissions in local storage - localStorage.userPerms = perms route('/profile') } @@ -17,7 +15,6 @@ export const login = async ({ teamToken, ctftimeToken }) => { case 'goodLogin': return { authToken: resp.data.authToken, - perms: resp.data.perms, } case 'badTokenVerification': return { @@ -38,7 +35,6 @@ export const login = async ({ teamToken, ctftimeToken }) => { export const logout = () => { localStorage.removeItem('token') localStorage.removeItem('userPerms') - return route('/') } @@ -51,7 +47,6 @@ export const verify = async ({ verifyToken }) => { case 'goodVerify': return { authToken: resp.data.authToken, - perms: resp.data.perms, } case 'goodEmailSet': return { @@ -79,8 +74,6 @@ export const register = async ({ switch (resp.kind) { case 'goodRegister': localStorage.setItem('token', resp.data.authToken) - localStorage.setItem('userPerms', resp.data.perms || 0) - 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 025199d..d599109 100644 --- a/packages/client/src/app.js +++ b/packages/client/src/app.js @@ -26,7 +26,7 @@ import AdminChallenges from './routes/admin/challs' import { ToastProvider } from './components/toast' import { navigateRef } from './history-hack' -import { getStoredPermissions, isAdmin } from './util/permissions' +import { hasChallsReadPermission } from './util/permissions' const LoggedOutRedir = const LoggedInRedir = @@ -64,7 +64,7 @@ function App({ classes }) { }, ] // Check if the user has admin permissions - if (isAdmin(getStoredPermissions())) { + if (hasChallsReadPermission()) { loggedInPaths.push({ element: , path: '/admin/challs', 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/components/pending-token.js b/packages/client/src/components/pending-token.js index c746fe4..0c172d7 100644 --- a/packages/client/src/components/pending-token.js +++ b/packages/client/src/components/pending-token.js @@ -3,7 +3,7 @@ import { useEffect, useState, useCallback } from 'preact/hooks' import { setAuthToken } from '../api/auth' import { pendingPrivateProfile } from '../api/profile' -const PendingToken = ({ authToken, perms }) => { +const PendingToken = ({ authToken }) => { const [user, setUser] = useState(null) useEffect(() => { ;(async () => { @@ -15,8 +15,8 @@ const PendingToken = ({ authToken, perms }) => { })() }, [authToken]) const handleLoginClick = useCallback(() => { - setAuthToken({ authToken, perms }) - }, [authToken, perms]) + setAuthToken({ authToken }) + }, [authToken]) if (!user) { return null } 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 ( { const [authToken, setAuthToken] = useState(null) - const [perms, setPerms] = useState(0) const [emailSet, setEmailSet] = useState(false) const [error, setError] = useState(null) @@ -21,7 +20,6 @@ const Verify = () => { const verifyRes = await verify({ verifyToken: qs.get('token') }) if (verifyRes.authToken) { setAuthToken(verifyRes.authToken) - setPerms(verifyRes.perms) } else if (verifyRes.emailSet) { setEmailSet(true) } else { @@ -56,7 +54,7 @@ const Verify = () => { ) } - return + return } export default Verify diff --git a/packages/client/src/util/permissions.js b/packages/client/src/util/permissions.js index 7988374..7495d3d 100644 --- a/packages/client/src/util/permissions.js +++ b/packages/client/src/util/permissions.js @@ -2,8 +2,12 @@ export const hasPermission = (userPerms, requiredPerms) => { return (userPerms & requiredPerms) === requiredPerms } -export const isAdmin = userPerms => { - return userPerms > 0 +export const hasChallsReadPermission = () => { + return getStoredPermissions() > 0 +} + +export const hasChallsWritePermission = () => { + return getStoredPermissions() > 1 } export const getStoredPermissions = () => { diff --git a/packages/server/src/api/auth/login.ts b/packages/server/src/api/auth/login.ts index 0adf01c..80c38a5 100644 --- a/packages/server/src/api/auth/login.ts +++ b/packages/server/src/api/auth/login.ts @@ -29,6 +29,6 @@ export default makeFastifyRoute( return res.badUnknownUser() } const authToken = await getToken(tokenKinds.auth, user.id) - return res.goodLogin({ authToken, perms: user.perms }) + return res.goodLogin({ authToken }) } ) diff --git a/packages/server/src/api/auth/verify.js b/packages/server/src/api/auth/verify.js index bf713ad..fb28a25 100644 --- a/packages/server/src/api/auth/verify.js +++ b/packages/server/src/api/auth/verify.js @@ -39,7 +39,7 @@ export default makeFastifyRoute(authVerifyPost, async ({ req, res }) => { auth.token.tokenKinds.auth, user.id ) - return res.goodVerify({ authToken, perms: user.perms }) + return res.goodVerify({ authToken }) } else if (tokenData.kind === 'update') { let result try { 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, }) }) diff --git a/packages/server/src/auth/register.ts b/packages/server/src/auth/register.ts index cccbc46..6af4ac6 100644 --- a/packages/server/src/auth/register.ts +++ b/packages/server/src/auth/register.ts @@ -47,5 +47,5 @@ export const register = async ( throw e } const authToken = await getToken(tokenKinds.auth, userUuid) - return res.goodRegister({ authToken, perms: 0 }) + return res.goodRegister({ authToken }) } diff --git a/packages/server/test/integration/auth.js b/packages/server/test/integration/auth.js index 3a640b3..af80b48 100644 --- a/packages/server/test/integration/auth.js +++ b/packages/server/test/integration/auth.js @@ -10,7 +10,6 @@ import { goodRegister, goodToken, goodUserUpdate, - goodLogin, } from '@rctf/api-types/responses' import * as auth from '../../src/auth' @@ -140,39 +139,3 @@ test('succeeds with goodUserUpdate', async () => { expect(respUser.email).toBe(testUser.email) expect(respUser.division).toBe(nextUser.division) }) - -test('register returns perms field', async () => { - config.email = null - - const newTestUser = generateTestUser() - const resp = await request(app.server) - .post(process.env.API_ENDPOINT + '/auth/register') - .send(newTestUser) - .expect(goodRegister.status) - - expect(resp.body.kind).toBe('goodRegister') - expect(resp.body.data.perms).toBe(0) - expect(typeof resp.body.data.authToken === 'string').toBe(true) -}) - -test('login returns perms field', async () => { - config.email = null - - const user = await database.users.getUserByEmail({ - email: testUser.email, - }) - - const teamToken = await auth.token.getToken( - auth.token.tokenKinds.team, - user.id - ) - - const resp = await request(app.server) - .post(process.env.API_ENDPOINT + '/auth/login') - .send({ teamToken }) - .expect(goodLogin.status) - - expect(resp.body.kind).toBe('goodLogin') - expect(resp.body.data.perms).toBe(user.perms) - expect(typeof resp.body.data.authToken === 'string').toBe(true) -})