mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(auth): Add optional CSRF protection (#697)
* fix(auth): Missing CSRF middleware Resolves LGTM alert/error for query js/missing-token-validation More info: https://lgtm.com/rules/1506064038914/
This commit is contained in:
@@ -62,6 +62,9 @@ components:
|
|||||||
applicationUrl:
|
applicationUrl:
|
||||||
type: string
|
type: string
|
||||||
example: https://os.example.com
|
example: https://os.example.com
|
||||||
|
csrfProtection:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
hideAvailable:
|
hideAvailable:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
"bowser": "^2.11.0",
|
"bowser": "^2.11.0",
|
||||||
"connect-typeorm": "^1.1.4",
|
"connect-typeorm": "^1.1.4",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
|
"csurf": "^1.11.0",
|
||||||
"email-templates": "^8.0.3",
|
"email-templates": "^8.0.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-openapi-validator": "^4.10.8",
|
"express-openapi-validator": "^4.10.8",
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
"@types/bcrypt": "^3.0.0",
|
"@types/bcrypt": "^3.0.0",
|
||||||
"@types/body-parser": "^1.19.0",
|
"@types/body-parser": "^1.19.0",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
|
"@types/csurf": "^1.11.0",
|
||||||
"@types/email-templates": "^8.0.0",
|
"@types/email-templates": "^8.0.0",
|
||||||
"@types/express": "^4.17.11",
|
"@types/express": "^4.17.11",
|
||||||
"@types/express-session": "^1.17.0",
|
"@types/express-session": "^1.17.0",
|
||||||
|
@@ -5,6 +5,7 @@ import { createConnection, getRepository } from 'typeorm';
|
|||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
|
import csurf from 'csurf';
|
||||||
import session, { Store } from 'express-session';
|
import session, { Store } from 'express-session';
|
||||||
import { TypeormStore } from 'connect-typeorm/out';
|
import { TypeormStore } from 'connect-typeorm/out';
|
||||||
import YAML from 'yamljs';
|
import YAML from 'yamljs';
|
||||||
@@ -78,8 +79,26 @@ app
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (settings.main.csrfProtection) {
|
||||||
|
server.use(
|
||||||
|
csurf({
|
||||||
|
cookie: {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: true,
|
||||||
|
secure: !dev,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
server.use((req, res, next) => {
|
||||||
|
res.cookie('XSRF-TOKEN', req.csrfToken(), {
|
||||||
|
sameSite: true,
|
||||||
|
secure: !dev,
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Setup sessions
|
// Set up sessions
|
||||||
const sessionRespository = getRepository(Session);
|
const sessionRespository = getRepository(Session);
|
||||||
server.use(
|
server.use(
|
||||||
'/api',
|
'/api',
|
||||||
|
@@ -48,6 +48,7 @@ export interface SonarrSettings extends DVRSettings {
|
|||||||
export interface MainSettings {
|
export interface MainSettings {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
applicationUrl: string;
|
applicationUrl: string;
|
||||||
|
csrfProtection: boolean;
|
||||||
defaultPermissions: number;
|
defaultPermissions: number;
|
||||||
hideAvailable: boolean;
|
hideAvailable: boolean;
|
||||||
}
|
}
|
||||||
@@ -155,6 +156,7 @@ class Settings {
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
applicationUrl: '',
|
applicationUrl: '',
|
||||||
hideAvailable: false,
|
hideAvailable: false,
|
||||||
|
csrfProtection: false,
|
||||||
defaultPermissions: Permission.REQUEST,
|
defaultPermissions: Permission.REQUEST,
|
||||||
},
|
},
|
||||||
plex: {
|
plex: {
|
||||||
|
@@ -27,6 +27,9 @@ const messages = defineMessages({
|
|||||||
toastSettingsFailure: 'Something went wrong saving settings.',
|
toastSettingsFailure: 'Something went wrong saving settings.',
|
||||||
defaultPermissions: 'Default User Permissions',
|
defaultPermissions: 'Default User Permissions',
|
||||||
hideAvailable: 'Hide available media',
|
hideAvailable: 'Hide available media',
|
||||||
|
csrfProtection: 'Enable CSRF Protection',
|
||||||
|
csrfProtectionTip:
|
||||||
|
'Sets external API access to read-only (Overseerr must be reloaded for changes to take effect)',
|
||||||
});
|
});
|
||||||
|
|
||||||
const SettingsMain: React.FC = () => {
|
const SettingsMain: React.FC = () => {
|
||||||
@@ -72,6 +75,7 @@ const SettingsMain: React.FC = () => {
|
|||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
applicationUrl: data?.applicationUrl,
|
applicationUrl: data?.applicationUrl,
|
||||||
|
csrfProtection: data?.csrfProtection,
|
||||||
defaultPermissions: data?.defaultPermissions ?? 0,
|
defaultPermissions: data?.defaultPermissions ?? 0,
|
||||||
hideAvailable: data?.hideAvailable,
|
hideAvailable: data?.hideAvailable,
|
||||||
}}
|
}}
|
||||||
@@ -80,6 +84,7 @@ const SettingsMain: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await axios.post('/api/v1/settings/main', {
|
await axios.post('/api/v1/settings/main', {
|
||||||
applicationUrl: values.applicationUrl,
|
applicationUrl: values.applicationUrl,
|
||||||
|
csrfProtection: values.csrfProtection,
|
||||||
defaultPermissions: values.defaultPermissions,
|
defaultPermissions: values.defaultPermissions,
|
||||||
hideAvailable: values.hideAvailable,
|
hideAvailable: values.hideAvailable,
|
||||||
});
|
});
|
||||||
@@ -165,6 +170,32 @@ const SettingsMain: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800">
|
||||||
|
<label
|
||||||
|
htmlFor="csrfProtection"
|
||||||
|
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="mr-2">
|
||||||
|
{intl.formatMessage(messages.csrfProtection)}
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-500">
|
||||||
|
{intl.formatMessage(messages.csrfProtectionTip)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<Field
|
||||||
|
type="checkbox"
|
||||||
|
id="csrfProtection"
|
||||||
|
name="csrfProtection"
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue('csrfProtection', !values.csrfProtection);
|
||||||
|
}}
|
||||||
|
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800">
|
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800">
|
||||||
<label
|
<label
|
||||||
htmlFor="name"
|
htmlFor="name"
|
||||||
|
@@ -378,6 +378,8 @@
|
|||||||
"components.Settings.autoapprovedrequests": "Send notifications for auto-approved requests",
|
"components.Settings.autoapprovedrequests": "Send notifications for auto-approved requests",
|
||||||
"components.Settings.cancelscan": "Cancel Scan",
|
"components.Settings.cancelscan": "Cancel Scan",
|
||||||
"components.Settings.copied": "Copied API key to clipboard",
|
"components.Settings.copied": "Copied API key to clipboard",
|
||||||
|
"components.Settings.csrfProtection": "Enable CSRF Protection",
|
||||||
|
"components.Settings.csrfProtectionTip": "Sets external API access to read-only (Overseerr must be reloaded for changes to take effect)",
|
||||||
"components.Settings.currentlibrary": "Current Library: {name}",
|
"components.Settings.currentlibrary": "Current Library: {name}",
|
||||||
"components.Settings.default": "Default",
|
"components.Settings.default": "Default",
|
||||||
"components.Settings.default4k": "Default 4K",
|
"components.Settings.default4k": "Default 4K",
|
||||||
|
40
yarn.lock
40
yarn.lock
@@ -2013,6 +2013,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/express" "*"
|
"@types/express" "*"
|
||||||
|
|
||||||
|
"@types/csurf@^1.11.0":
|
||||||
|
version "1.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/csurf/-/csurf-1.11.0.tgz#2809e89f55f12a2df8cd2826c06dfd66600dd14d"
|
||||||
|
integrity sha512-IwqGRWImcbIdwumGprYR0EgIZ7GAklOIGaNqe2u7jb0YUilg7yrrxXth11VA/AJK8wQWYHxTQagkCE75oaoBvQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/express-serve-static-core" "*"
|
||||||
|
|
||||||
"@types/debug@0.0.31":
|
"@types/debug@0.0.31":
|
||||||
version "0.0.31"
|
version "0.0.31"
|
||||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.31.tgz#bac8d8aab6a823e91deb7f79083b2a35fa638f33"
|
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.31.tgz#bac8d8aab6a823e91deb7f79083b2a35fa638f33"
|
||||||
@@ -4647,6 +4654,15 @@ crypto-random-string@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
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"
|
||||||
|
|
||||||
css-blank-pseudo@^0.1.4:
|
css-blank-pseudo@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
||||||
@@ -4822,6 +4838,16 @@ csstype@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.2.tgz#ee5ff8f208c8cd613b389f7b222c9801ca62b3f7"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.2.tgz#ee5ff8f208c8cd613b389f7b222c9801ca62b3f7"
|
||||||
integrity sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==
|
integrity sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
cyclist@^1.0.1:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
@@ -7053,7 +7079,7 @@ http-errors@1.7.2:
|
|||||||
statuses ">= 1.5.0 < 2"
|
statuses ">= 1.5.0 < 2"
|
||||||
toidentifier "1.0.0"
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
http-errors@1.7.3, http-errors@~1.7.2:
|
http-errors@1.7.3, http-errors@~1.7.2, http-errors@~1.7.3:
|
||||||
version "1.7.3"
|
version "1.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||||
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||||
@@ -12188,6 +12214,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||||||
hash-base "^3.0.0"
|
hash-base "^3.0.0"
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
|
|
||||||
|
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-async@^2.2.0:
|
run-async@^2.2.0:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
||||||
@@ -13628,6 +13659,11 @@ tslib@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.2.tgz#462295631185db44b21b1ea3615b63cd1c038242"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.2.tgz#462295631185db44b21b1ea3615b63cd1c038242"
|
||||||
integrity sha512-wAH28hcEKwna96/UacuWaVspVLkg4x1aDM9JlzqaQTOFczCktkVAb5fmXChgandR1EraDPs2w8P+ozM+oafwxg==
|
integrity sha512-wAH28hcEKwna96/UacuWaVspVLkg4x1aDM9JlzqaQTOFczCktkVAb5fmXChgandR1EraDPs2w8P+ozM+oafwxg==
|
||||||
|
|
||||||
|
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.17.1:
|
tsutils@^3.17.1:
|
||||||
version "3.17.1"
|
version "3.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
||||||
@@ -13771,7 +13807,7 @@ uid-number@0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
|
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
|
||||||
integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
|
integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
|
||||||
|
|
||||||
uid-safe@~2.1.5:
|
uid-safe@2.1.5, uid-safe@~2.1.5:
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
||||||
integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==
|
integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==
|
||||||
|
Reference in New Issue
Block a user