mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
Compare commits
1 Commits
e5c95e00b9
...
preview-fr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75651f63ad |
@@ -7,6 +7,7 @@
|
||||
"applicationTitle": "Overseerr",
|
||||
"applicationUrl": "",
|
||||
"csrfProtection": false,
|
||||
"cspFrameAncestorDomains": "",
|
||||
"cacheImages": false,
|
||||
"defaultPermissions": 32,
|
||||
"defaultQuotas": {
|
||||
|
||||
@@ -168,6 +168,9 @@ components:
|
||||
csrfProtection:
|
||||
type: boolean
|
||||
example: false
|
||||
cspFrameAncestorDomains:
|
||||
type: string
|
||||
example: 'example.com'
|
||||
hideAvailable:
|
||||
type: boolean
|
||||
example: false
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"express-session": "1.17.3",
|
||||
"formik": "^2.4.6",
|
||||
"gravatar-url": "3.1.0",
|
||||
"helmet": "^7.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"mime": "3",
|
||||
"next": "^14.2.4",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -95,6 +95,9 @@ importers:
|
||||
gravatar-url:
|
||||
specifier: 3.1.0
|
||||
version: 3.1.0
|
||||
helmet:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
lodash:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
@@ -5292,6 +5295,10 @@ packages:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
helmet@7.1.0:
|
||||
resolution: {integrity: sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
hermes-estree@0.19.1:
|
||||
resolution: {integrity: sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==}
|
||||
|
||||
@@ -15595,6 +15602,8 @@ snapshots:
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
helmet@7.1.0: {}
|
||||
|
||||
hermes-estree@0.19.1: {}
|
||||
|
||||
hermes-estree@0.20.1: {}
|
||||
|
||||
@@ -32,6 +32,7 @@ import express from 'express';
|
||||
import * as OpenApiValidator from 'express-openapi-validator';
|
||||
import type { Store } from 'express-session';
|
||||
import session from 'express-session';
|
||||
import helmet from 'helmet';
|
||||
import next from 'next';
|
||||
import dns from 'node:dns';
|
||||
import net from 'node:net';
|
||||
@@ -159,6 +160,28 @@ app
|
||||
});
|
||||
}
|
||||
|
||||
// Setup Content-Security-Policy
|
||||
server.use(
|
||||
helmet.contentSecurityPolicy({
|
||||
useDefaults: false,
|
||||
directives: {
|
||||
'default-src': ["'self'", "'unsafe-inline'"],
|
||||
'script-src': [
|
||||
"'self'",
|
||||
"'unsafe-inline'",
|
||||
...(dev ? ["'unsafe-eval'"] : []),
|
||||
],
|
||||
'img-src': ["'self'", "'unsafe-inline'", 'data:', 'blob:', '*'],
|
||||
'frame-ancestors': [
|
||||
"'self'",
|
||||
...(settings.main.cspFrameAncestorDomains
|
||||
? [settings.main.cspFrameAncestorDomains]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Set up sessions
|
||||
const sessionRespository = getRepository(Session);
|
||||
server.use(
|
||||
@@ -170,8 +193,12 @@ app
|
||||
cookie: {
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30,
|
||||
httpOnly: true,
|
||||
sameSite: settings.main.csrfProtection ? 'strict' : 'lax',
|
||||
secure: 'auto',
|
||||
sameSite: settings.main.csrfProtection
|
||||
? 'strict'
|
||||
: settings.main.cspFrameAncestorDomains
|
||||
? 'none'
|
||||
: 'lax',
|
||||
secure: settings.main.cspFrameAncestorDomains ? true : 'auto',
|
||||
},
|
||||
store: new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
|
||||
@@ -104,6 +104,7 @@ export interface MainSettings {
|
||||
applicationTitle: string;
|
||||
applicationUrl: string;
|
||||
csrfProtection: boolean;
|
||||
cspFrameAncestorDomains: string;
|
||||
cacheImages: boolean;
|
||||
defaultPermissions: number;
|
||||
defaultQuotas: {
|
||||
@@ -310,6 +311,7 @@ class Settings {
|
||||
applicationTitle: 'Jellyseerr',
|
||||
applicationUrl: '',
|
||||
csrfProtection: false,
|
||||
cspFrameAncestorDomains: '',
|
||||
cacheImages: false,
|
||||
defaultPermissions: Permission.REQUEST,
|
||||
defaultQuotas: {
|
||||
|
||||
@@ -13,7 +13,8 @@ class RestartFlag {
|
||||
|
||||
return (
|
||||
this.settings.csrfProtection !== settings.csrfProtection ||
|
||||
this.settings.trustProxy !== settings.trustProxy
|
||||
this.settings.trustProxy !== settings.trustProxy ||
|
||||
this.settings.cspFrameAncestorDomains !== settings.cspFrameAncestorDomains
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ const messages = defineMessages('components.Settings.SettingsMain', {
|
||||
csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)',
|
||||
csrfProtectionHoverTip:
|
||||
'Do NOT enable this setting unless you understand what you are doing!',
|
||||
cspFrameAncestorDomains: 'Frame-Ancestor Domains',
|
||||
cspFrameAncestorDomainsTip:
|
||||
'Domains to allow embedding Jellyseer as iframe, object or embed. Incompatible with CSRF-Protection',
|
||||
cacheImages: 'Enable Image Caching',
|
||||
cacheImagesTip:
|
||||
'Cache externally sourced images (requires a significant amount of disk space)',
|
||||
@@ -130,6 +133,7 @@ const SettingsMain = () => {
|
||||
applicationTitle: data?.applicationTitle,
|
||||
applicationUrl: data?.applicationUrl,
|
||||
csrfProtection: data?.csrfProtection,
|
||||
cspFrameAncestorDomains: data?.cspFrameAncestorDomains,
|
||||
hideAvailable: data?.hideAvailable,
|
||||
locale: data?.locale ?? 'en',
|
||||
region: data?.region,
|
||||
@@ -151,6 +155,7 @@ const SettingsMain = () => {
|
||||
applicationTitle: values.applicationTitle,
|
||||
applicationUrl: values.applicationUrl,
|
||||
csrfProtection: values.csrfProtection,
|
||||
cspFrameAncestorDomains: values.cspFrameAncestorDomains,
|
||||
hideAvailable: values.hideAvailable,
|
||||
locale: values.locale,
|
||||
region: values.region,
|
||||
@@ -318,6 +323,31 @@ const SettingsMain = () => {
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="cspFrameAncestorDomains"
|
||||
className="text-label"
|
||||
>
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.cspFrameAncestorDomains)}
|
||||
</span>
|
||||
<SettingsBadge badgeType="advanced" className="mr-2" />
|
||||
<SettingsBadge badgeType="restartRequired" />
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.cspFrameAncestorDomainsTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="cspFrameAncestorDomains"
|
||||
name="cspFrameAncestorDomains"
|
||||
type="text"
|
||||
disabled={values.csrfProtection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="cacheImages" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
|
||||
@@ -873,6 +873,8 @@
|
||||
"components.Settings.SettingsMain.applicationurl": "Application URL",
|
||||
"components.Settings.SettingsMain.cacheImages": "Enable Image Caching",
|
||||
"components.Settings.SettingsMain.cacheImagesTip": "Cache externally sourced images (requires a significant amount of disk space)",
|
||||
"components.Settings.SettingsMain.cspFrameAncestorDomains": "Frame-Ancestor Domains",
|
||||
"components.Settings.SettingsMain.cspFrameAncestorDomainsTip": "Domains to allow embedding Jellyseer as iframe, object or embed. Incompatible with CSRF-Protection",
|
||||
"components.Settings.SettingsMain.csrfProtection": "Enable CSRF Protection",
|
||||
"components.Settings.SettingsMain.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
|
||||
@@ -1030,6 +1032,7 @@
|
||||
"components.Settings.save": "Save Changes",
|
||||
"components.Settings.saving": "Saving…",
|
||||
"components.Settings.scan": "Sync Libraries",
|
||||
"components.Settings.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
|
||||
"components.Settings.scanning": "Syncing…",
|
||||
"components.Settings.serverLocal": "local",
|
||||
"components.Settings.serverRemote": "remote",
|
||||
@@ -1050,6 +1053,7 @@
|
||||
"components.Settings.tautulliSettings": "Tautulli Settings",
|
||||
"components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.",
|
||||
"components.Settings.timeout": "Timeout",
|
||||
"components.Settings.tip": "Tip",
|
||||
"components.Settings.toastPlexConnecting": "Attempting to connect to Plex…",
|
||||
"components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.",
|
||||
"components.Settings.toastPlexConnectingSuccess": "Plex connection established successfully!",
|
||||
@@ -1079,7 +1083,6 @@
|
||||
"components.Setup.continue": "Continue",
|
||||
"components.Setup.finish": "Finish Setup",
|
||||
"components.Setup.finishing": "Finishing…",
|
||||
"components.Setup.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
|
||||
"components.Setup.servertype": "Choose Server Type",
|
||||
"components.Setup.setup": "Setup",
|
||||
"components.Setup.signin": "Sign In",
|
||||
@@ -1088,7 +1091,6 @@
|
||||
"components.Setup.signinWithJellyfin": "Enter your Jellyfin details",
|
||||
"components.Setup.signinWithPlex": "Enter your Plex details",
|
||||
"components.Setup.subtitle": "Get started by choosing your media server",
|
||||
"components.Setup.tip": "Tip",
|
||||
"components.Setup.welcome": "Welcome to Jellyseerr",
|
||||
"components.StatusBadge.managemedia": "Manage {mediaType}",
|
||||
"components.StatusBadge.openinarr": "Open in {arr}",
|
||||
@@ -1233,6 +1235,7 @@
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Saving…",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Series Request Limit",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Something went wrong while saving settings.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "This email is already taken!",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Settings saved successfully!",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.user": "User",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "You must provide a valid Discord user ID",
|
||||
@@ -1312,6 +1315,7 @@
|
||||
"components.UserProfile.seriesrequest": "Series Requests",
|
||||
"components.UserProfile.totalrequests": "Total Requests",
|
||||
"components.UserProfile.unlimited": "Unlimited",
|
||||
"i18n.addToBlacklist": "Add to Blacklist",
|
||||
"i18n.advanced": "Advanced",
|
||||
"i18n.all": "All",
|
||||
"i18n.approve": "Approve",
|
||||
|
||||
@@ -17,11 +17,13 @@ import { MediaServerType } from '@server/constants/server';
|
||||
import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces';
|
||||
import type { AppInitialProps, AppProps } from 'next/app';
|
||||
import App from 'next/app';
|
||||
import { Inter } from 'next/font/google';
|
||||
import Head from 'next/head';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { ToastProvider } from 'react-toast-notifications';
|
||||
import { SWRConfig } from 'swr';
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
|
||||
@@ -136,47 +138,49 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher: async (resource, init) => {
|
||||
const res = await fetch(resource, init);
|
||||
if (!res.ok) throw new Error();
|
||||
return await res.json();
|
||||
},
|
||||
fallback: {
|
||||
'/api/v1/auth/me': user,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LanguageContext.Provider value={{ locale: currentLocale, setLocale }}>
|
||||
<IntlProvider
|
||||
locale={currentLocale}
|
||||
defaultLocale="en"
|
||||
messages={loadedMessages}
|
||||
>
|
||||
<LoadingBar />
|
||||
<SettingsProvider currentSettings={currentSettings}>
|
||||
<InteractionProvider>
|
||||
<ToastProvider components={{ Toast, ToastContainer }}>
|
||||
<Head>
|
||||
<title>{currentSettings.applicationTitle}</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="initial-scale=1, viewport-fit=cover, width=device-width"
|
||||
></meta>
|
||||
<PWAHeader
|
||||
applicationTitle={currentSettings.applicationTitle}
|
||||
/>
|
||||
</Head>
|
||||
<StatusChecker />
|
||||
<ServiceWorkerSetup />
|
||||
<UserContext initialUser={user}>{component}</UserContext>
|
||||
</ToastProvider>
|
||||
</InteractionProvider>
|
||||
</SettingsProvider>
|
||||
</IntlProvider>
|
||||
</LanguageContext.Provider>
|
||||
</SWRConfig>
|
||||
<main className={inter.className}>
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher: async (resource, init) => {
|
||||
const res = await fetch(resource, init);
|
||||
if (!res.ok) throw new Error();
|
||||
return await res.json();
|
||||
},
|
||||
fallback: {
|
||||
'/api/v1/auth/me': user,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LanguageContext.Provider value={{ locale: currentLocale, setLocale }}>
|
||||
<IntlProvider
|
||||
locale={currentLocale}
|
||||
defaultLocale="en"
|
||||
messages={loadedMessages}
|
||||
>
|
||||
<LoadingBar />
|
||||
<SettingsProvider currentSettings={currentSettings}>
|
||||
<InteractionProvider>
|
||||
<ToastProvider components={{ Toast, ToastContainer }}>
|
||||
<Head>
|
||||
<title>{currentSettings.applicationTitle}</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="initial-scale=1, viewport-fit=cover, width=device-width"
|
||||
></meta>
|
||||
<PWAHeader
|
||||
applicationTitle={currentSettings.applicationTitle}
|
||||
/>
|
||||
</Head>
|
||||
<StatusChecker />
|
||||
<ServiceWorkerSetup />
|
||||
<UserContext initialUser={user}>{component}</UserContext>
|
||||
</ToastProvider>
|
||||
</InteractionProvider>
|
||||
</SettingsProvider>
|
||||
</IntlProvider>
|
||||
</LanguageContext.Provider>
|
||||
</SWRConfig>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,13 +13,7 @@ class MyDocument extends Document {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
|
||||
/>
|
||||
</Head>
|
||||
<Head></Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
}
|
||||
|
||||
@layer components {
|
||||
*:disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
height: calc(4rem + env(safe-area-inset-top));
|
||||
|
||||
Reference in New Issue
Block a user