diff --git a/README.md b/README.md index 7d4474ebc..e7be19425 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ This will run a demo version, which you can turn into a fully licenced version b ## Versions History +### Version 4.14.1 (hotfix) + +- Remove CSRF code, causing random issues + ### Version 4.14.0 - Upgrade to React 18 diff --git a/backend/package.json b/backend/package.json index b46d928e7..659842d54 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@retrospected/backend", - "version": "4.14.0", + "version": "4.14.1", "license": "GNU GPLv3", "private": true, "scripts": { @@ -24,7 +24,6 @@ "@types/bcryptjs": "2.4.2", "@types/connect-redis": "0.0.18", "@types/crypto-js": "4.1.1", - "@types/csurf": "1.11.2", "@types/express": "4.17.13", "@types/express-mung": "0.5.2", "@types/express-rate-limit": "6.0.0", @@ -50,7 +49,6 @@ "connect-redis": "6.1.3", "copyfiles": "2.4.1", "crypto-js": "4.1.1", - "csurf": "1.11.0", "date-fns": "2.28.0", "dotenv": "16.0.0", "eslint": "8.13.0", diff --git a/backend/src/admin/router.ts b/backend/src/admin/router.ts index 2754b9f18..2022838bc 100644 --- a/backend/src/admin/router.ts +++ b/backend/src/admin/router.ts @@ -8,11 +8,9 @@ import config from '../config'; import { isLicenced } from '../security/is-licenced'; import { AdminChangePasswordPayload, BackendCapabilities } from '../common'; import { getIdentityFromRequest, hashPassword } from '../utils'; -import csurf from 'csurf'; import { canSendEmails } from '../email/utils'; const router = express.Router(); -const csrfProtection = csurf(); router.get('/self-hosting', async (_, res) => { const licence = await isLicenced(); @@ -45,7 +43,7 @@ router.get('/users', async (req, res) => { res.send(users.map((u) => u.toJson())); }); -router.patch('/user', csrfProtection, async (req, res) => { +router.patch('/user', async (req, res) => { const authIdentity = await getIdentityFromRequest(req); if (!authIdentity || authIdentity.user.email !== config.SELF_HOSTED_ADMIN) { return res.status(403).send('You are not allowed to do this'); diff --git a/backend/src/index.ts b/backend/src/index.ts index 1bb15b983..0d3e75d1a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,7 +3,6 @@ import * as socketIo from 'socket.io'; import { createAdapter } from 'socket.io-redis'; import { createClient } from 'redis'; import connectRedis from 'connect-redis'; -import csurf from 'csurf'; import http from 'http'; import chalk from 'chalk'; import db from './db'; @@ -129,9 +128,6 @@ const heavyLoadLimiter = rateLimit({ }, }); -// CSRF Protection -const csrfProtection = csurf(); - // Sentry setupSentryRequestHandler(app); @@ -220,10 +216,6 @@ if (process.env.NODE_ENV !== 'production') { ); } -app.get('/api/csrf', csrfProtection, (req, res) => { - res.json({ token: req.csrfToken() }); -}); - app.get('/api/ping', (req, res) => { res.send('pong'); }); @@ -257,36 +249,31 @@ db().then(() => { app.use('/api/slack', slackRouter()); // Create session - app.post( - '/api/create', - csrfProtection, - heavyLoadLimiter, - async (req, res) => { - const identity = await getIdentityFromRequest(req); - const payload: CreateSessionPayload = req.body; - setScope(async (scope) => { - if (identity) { - try { - const session = await createSession( - identity.user, - payload.encryptedCheck - ); - res.status(200).send(session); - } catch (err: unknown) { - if (err instanceof QueryFailedError) { - reportQueryError(scope, err); - } - res.status(500).send(); - throw err; + app.post('/api/create', heavyLoadLimiter, async (req, res) => { + const identity = await getIdentityFromRequest(req); + const payload: CreateSessionPayload = req.body; + setScope(async (scope) => { + if (identity) { + try { + const session = await createSession( + identity.user, + payload.encryptedCheck + ); + res.status(200).send(session); + } catch (err: unknown) { + if (err instanceof QueryFailedError) { + reportQueryError(scope, err); } - } else { - res - .status(401) - .send('You must be logged in in order to create a session'); + res.status(500).send(); + throw err; } - }); - } - ); + } else { + res + .status(401) + .send('You must be logged in in order to create a session'); + } + }); + }); app.post('/api/logout', async (req, res, next) => { req.logout(); @@ -307,7 +294,7 @@ db().then(() => { } }); - app.delete('/api/me', csrfProtection, heavyLoadLimiter, async (req, res) => { + app.delete('/api/me', heavyLoadLimiter, async (req, res) => { const user = await getUserViewFromRequest(req); if (user) { const result = await deleteAccount( @@ -339,27 +326,22 @@ db().then(() => { } }); - app.delete( - '/api/session/:sessionId', - csrfProtection, - heavyLoadLimiter, - async (req, res) => { - const sessionId = req.params.sessionId; - const identity = await getIdentityFromRequest(req); - if (identity) { - const success = await deleteSessions(identity.id, sessionId); - if (success) { - res.status(200).send(); - } else { - res.status(403).send(); - } + app.delete('/api/session/:sessionId', heavyLoadLimiter, async (req, res) => { + const sessionId = req.params.sessionId; + const identity = await getIdentityFromRequest(req); + if (identity) { + const success = await deleteSessions(identity.id, sessionId); + if (success) { + res.status(200).send(); } else { res.status(403).send(); } + } else { + res.status(403).send(); } - ); + }); - app.post('/api/me/language', csrfProtection, async (req, res) => { + app.post('/api/me/language', async (req, res) => { const user = await getUserViewFromRequest(req); if (user) { await updateUser(user.id, { diff --git a/backend/src/stripe/router.ts b/backend/src/stripe/router.ts index 4a89e9eac..00637c809 100644 --- a/backend/src/stripe/router.ts +++ b/backend/src/stripe/router.ts @@ -20,15 +20,11 @@ import { saveSubscription, startTrial, } from '../db/actions/subscriptions'; -import csurf from 'csurf'; const stripe = new Stripe(config.STRIPE_SECRET, { apiVersion: '2020-08-27', } as Stripe.StripeConfig); -// CSRF Protection -const csrfProtection = csurf(); - function stripeRouter(): Router { const router = express.Router(); @@ -177,7 +173,7 @@ function stripeRouter(): Router { res.sendStatus(200); }); - router.post('/create-checkout-session', csrfProtection, async (req, res) => { + router.post('/create-checkout-session', async (req, res) => { const payload = req.body as CreateSubscriptionPayload; const { yearly, ...actualPayload } = payload; const identity = await getIdentityFromRequest(req); @@ -262,7 +258,7 @@ function stripeRouter(): Router { res.status(401).send(); }); - router.patch('/members', csrfProtection, async (req, res) => { + router.patch('/members', async (req, res) => { const identity = await getIdentityFromRequest(req); if (identity) { const subscription = await getActiveSubscription(identity.user.id); @@ -280,7 +276,7 @@ function stripeRouter(): Router { return res.status(200).send(isValidDomain(domain)); }); - router.post('/start-trial', csrfProtection, async (req, res) => { + router.post('/start-trial', async (req, res) => { const identity = await getIdentityFromRequest(req); if (identity) { const updatedUser = await startTrial(identity.user.id); diff --git a/backend/yarn.lock b/backend/yarn.lock index 718e5d5b2..1294d5ea6 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -781,13 +781,6 @@ resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== -"@types/csurf@1.11.2": - version "1.11.2" - resolved "https://registry.yarnpkg.com/@types/csurf/-/csurf-1.11.2.tgz#c1cba70f7af653c508b28db047e6c1be72411345" - integrity sha512-9bc98EnwmC1S0aSJiA8rWwXtgXtXHHOQOsGHptImxFgqm6CeH+mIOunHRg6+/eg2tlmDMX3tY7XrWxo2M/nUNQ== - dependencies: - "@types/express-serve-static-core" "*" - "@types/express-mung@0.5.2": version "0.5.2" resolved "https://registry.yarnpkg.com/@types/express-mung/-/express-mung-0.5.2.tgz#d0df337a6c770f8dada08ddf101757c3698f8c50" @@ -803,7 +796,7 @@ dependencies: express-rate-limit "*" -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": +"@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== @@ -1709,11 +1702,6 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - cookie@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" @@ -1774,15 +1762,6 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -csrf@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/csrf/-/csrf-3.1.0.tgz#ec75e9656d004d674b8ef5ba47b41fbfd6cb9c30" - integrity sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w== - dependencies: - rndm "1.2.0" - tsscmp "1.0.6" - uid-safe "2.1.5" - cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -1800,16 +1779,6 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csurf@1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/csurf/-/csurf-1.11.0.tgz#ab0c3c6634634192bd3d6f4b861be20800eeb61a" - integrity sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ== - dependencies: - cookie "0.4.0" - cookie-signature "1.0.6" - csrf "3.1.0" - http-errors "~1.7.3" - data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -2638,17 +2607,6 @@ http-errors@1.8.1: statuses ">= 1.5.0 < 2" toidentifier "1.0.1" -http-errors@~1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -4384,11 +4342,6 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rndm@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/rndm/-/rndm-1.2.0.tgz#f33fe9cfb52bbfd520aa18323bc65db110a1b76c" - integrity sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w= - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -4476,11 +4429,6 @@ serve-static@1.14.2: parseurl "~1.3.3" send "0.17.2" -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -4805,11 +4753,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -4886,11 +4829,6 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tsscmp@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" - integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -4980,7 +4918,7 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.3.tgz#9aa82ca22419ba4c0137642ba0df800cb06e0471" integrity sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg== -uid-safe@2.1.5, uid-safe@~2.1.5: +uid-safe@~2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== diff --git a/doc.md b/doc.md deleted file mode 100644 index d0044c544..000000000 --- a/doc.md +++ /dev/null @@ -1,7 +0,0 @@ -# Various doc - -## CSRF - -http://expressjs.com/en/resources/middleware/csurf.html - -https://www.stackhawk.com/blog/react-csrf-protection-guide-examples-and-how-to-enable-it/ diff --git a/docs/package.json b/docs/package.json index c78dfde16..5b04a7afe 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "docs", - "version": "4.14.0", + "version": "4.14.1", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/frontend/package.json b/frontend/package.json index 403a8abf0..4e501ca48 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@retrospected/frontend", - "version": "4.14.0", + "version": "4.14.1", "license": "GNU GPLv3", "private": true, "dependencies": { diff --git a/frontend/src/api/fetch.ts b/frontend/src/api/fetch.ts index 613f86b99..6fd0e4623 100644 --- a/frontend/src/api/fetch.ts +++ b/frontend/src/api/fetch.ts @@ -1,15 +1,5 @@ import * as Sentry from '@sentry/browser'; -export let csrf = ''; - -export async function loadCsrfToken() { - const data = await fetchGet<{ token: string } | null>('/api/csrf', null); - const token = data ? data.token : null; - if (token) { - csrf = token; - } -} - export function requestConfig(): Partial { return { mode: 'same-origin', @@ -17,7 +7,6 @@ export function requestConfig(): Partial { credentials: 'same-origin', headers: { 'Content-Type': 'application/json', - 'CSRF-Token': csrf, }, redirect: 'follow', referrer: 'same-origin', diff --git a/frontend/src/auth/modal/AnonAuth.tsx b/frontend/src/auth/modal/AnonAuth.tsx index 35464ca9f..a4ea70964 100644 --- a/frontend/src/auth/modal/AnonAuth.tsx +++ b/frontend/src/auth/modal/AnonAuth.tsx @@ -6,7 +6,6 @@ import useTranslations, { useLanguage } from '../../translations'; import { anonymousLogin, updateLanguage } from '../../api'; import { FullUser } from 'common'; import Wrapper from './Wrapper'; -import { loadCsrfToken } from '../../api/fetch'; interface AnonAuthProps { onClose: () => void; @@ -28,7 +27,6 @@ const AnonAuth = ({ onClose, onUser }: AnonAuthProps) => { setError('Your anonymous account is not valid.'); return; } - await loadCsrfToken(); // Because the user changed, so the CSRF token must be updated const updatedUser = await updateLanguage(language.value); onUser(updatedUser); if (onClose) { diff --git a/frontend/src/auth/modal/SocialAuth.tsx b/frontend/src/auth/modal/SocialAuth.tsx index 5865ae398..d2a968b40 100644 --- a/frontend/src/auth/modal/SocialAuth.tsx +++ b/frontend/src/auth/modal/SocialAuth.tsx @@ -15,7 +15,6 @@ import Wrapper from './Wrapper'; import SlackLoginButton from './social/SlackLoginButton'; import OktaLoginButton from './social/OktaLoginButton'; import useOAuthAvailabilities from '../../global/useOAuthAvailabilities'; -import { loadCsrfToken } from '../../api/fetch'; const API_URL = '/api/auth'; @@ -65,7 +64,6 @@ function SocialAuth({ onClose, onUser }: SocialAuthProps) { const s = io(); setSocket(s); s.on('auth', async (_user: FullUser) => { - await loadCsrfToken(); // Because the user changed, so the CSRF token must be updated const updatedUser = await updateLanguage(language.value); onUser(updatedUser); if (windowRef.current) { diff --git a/frontend/src/global/GlobalProvider.tsx b/frontend/src/global/GlobalProvider.tsx index 112be9b20..5296e728e 100644 --- a/frontend/src/global/GlobalProvider.tsx +++ b/frontend/src/global/GlobalProvider.tsx @@ -2,14 +2,12 @@ import { useEffect } from 'react'; import { fetchBackendCapabilities } from '../api'; import { useSetRecoilState } from 'recoil'; import { backendCapabilitiesState } from './state'; -import { loadCsrfToken } from '../api/fetch'; const GlobalProvider: React.FC = ({ children }) => { const setBackendCapabilities = useSetRecoilState(backendCapabilitiesState); useEffect(() => { async function loadGlobal() { - await loadCsrfToken(); // Make sure the CSRF token is loaded before anything else const infos = await fetchBackendCapabilities(); if (infos) { setBackendCapabilities(infos); diff --git a/integration/package.json b/integration/package.json index f57acf5e8..0de0e346e 100644 --- a/integration/package.json +++ b/integration/package.json @@ -1,6 +1,6 @@ { "name": "retro-board-integration", - "version": "4.14.0", + "version": "4.14.1", "description": "Integrations tests", "main": "index.js", "directories": { diff --git a/package.json b/package.json index 0a96a2b72..f43751342 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "retrospected", - "version": "4.14.0", + "version": "4.14.1", "description": "An agile retrospective board - Powering www.retrospected.com", "private": true, "scripts": {