diff --git a/overseerr-api.yml b/overseerr-api.yml index f3be28ad2..641ce5d7c 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -164,12 +164,6 @@ components: applicationUrl: type: string example: https://os.example.com - trustProxy: - type: boolean - example: true - csrfProtection: - type: boolean - example: false hideAvailable: type: boolean example: false @@ -191,12 +185,21 @@ components: enableSpecialEpisodes: type: boolean example: false + NetworkSettings: + type: object + properties: + csrfProtection: + type: boolean + example: false forceIpv4First: type: boolean example: false dnsServers: type: string example: '1.1.1.1' + trustProxy: + type: boolean + example: true PlexLibrary: type: object properties: @@ -2045,6 +2048,37 @@ paths: application/json: schema: $ref: '#/components/schemas/MainSettings' + /settings/network: + get: + summary: Get network settings + description: Retrieves all network settings in a JSON object. + tags: + - settings + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MainSettings' + post: + summary: Update network settings + description: Updates network settings with the provided values. + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkSettings' + responses: + '200': + description: 'Values were sucessfully updated' + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkSettings' /settings/main/regenerate: post: summary: Get main settings with newly-generated API key diff --git a/server/index.ts b/server/index.ts index dde0d5083..e4e872ab6 100644 --- a/server/index.ts +++ b/server/index.ts @@ -72,23 +72,26 @@ app // Load Settings const settings = await getSettings().load(); - restartFlag.initializeSettings(settings.main); + restartFlag.initializeSettings(settings); // Check if we force IPv4 first - if (process.env.forceIpv4First === 'true' || settings.main.forceIpv4First) { + if ( + process.env.forceIpv4First === 'true' || + settings.network.forceIpv4First + ) { dns.setDefaultResultOrder('ipv4first'); net.setDefaultAutoSelectFamily(false); } - if (settings.main.dnsServers.trim() !== '') { + if (settings.network.dnsServers.trim() !== '') { dns.setServers( - settings.main.dnsServers.split(',').map((server) => server.trim()) + settings.network.dnsServers.split(',').map((server) => server.trim()) ); } // Register HTTP proxy - if (settings.main.proxy.enabled) { - await createCustomProxyAgent(settings.main.proxy); + if (settings.network.proxy.enabled) { + await createCustomProxyAgent(settings.network.proxy); } // Migrate library types @@ -143,7 +146,7 @@ app await DiscoverSlider.bootstrapSliders(); const server = express(); - if (settings.main.trustProxy) { + if (settings.network.trustProxy) { server.enable('trust proxy'); } server.use(cookieParser()); @@ -164,7 +167,7 @@ app next(); } }); - if (settings.main.csrfProtection) { + if (settings.network.csrfProtection) { server.use( csurf({ cookie: { @@ -194,7 +197,7 @@ app cookie: { maxAge: 1000 * 60 * 60 * 24 * 30, httpOnly: true, - sameSite: settings.main.csrfProtection ? 'strict' : 'lax', + sameSite: settings.network.csrfProtection ? 'strict' : 'lax', secure: 'auto', }, store: new TypeormStore({ diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 343c01e2f..258dfe2f4 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -115,7 +115,6 @@ export interface MainSettings { apiKey: string; applicationTitle: string; applicationUrl: string; - csrfProtection: boolean; cacheImages: boolean; defaultPermissions: number; defaultQuotas: { @@ -128,13 +127,17 @@ export interface MainSettings { discoverRegion: string; streamingRegion: string; originalLanguage: string; - trustProxy: boolean; mediaServerType: number; partialRequestsEnabled: boolean; enableSpecialEpisodes: boolean; + locale: string; +} + +export interface NetworkSettings { + csrfProtection: boolean; forceIpv4First: boolean; dnsServers: string; - locale: string; + trustProxy: boolean; proxy: ProxySettings; } @@ -313,6 +316,7 @@ export interface AllSettings { public: PublicSettings; notifications: NotificationSettings; jobs: Record; + network: NetworkSettings; } const SETTINGS_PATH = process.env.CONFIG_DIRECTORY @@ -331,7 +335,6 @@ class Settings { apiKey: '', applicationTitle: 'Jellyseerr', applicationUrl: '', - csrfProtection: false, cacheImages: false, defaultPermissions: Permission.REQUEST, defaultQuotas: { @@ -344,23 +347,10 @@ class Settings { discoverRegion: '', streamingRegion: '', originalLanguage: '', - trustProxy: false, mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, enableSpecialEpisodes: false, - forceIpv4First: false, - dnsServers: '', locale: 'en', - proxy: { - enabled: false, - hostname: '', - port: 8080, - useSsl: false, - user: '', - password: '', - bypassFilter: '', - bypassLocalAddresses: true, - }, }, plex: { name: '', @@ -513,6 +503,22 @@ class Settings { schedule: '0 0 5 * * *', }, }, + network: { + csrfProtection: false, + trustProxy: false, + forceIpv4First: false, + dnsServers: '', + proxy: { + enabled: false, + hostname: '', + port: 8080, + useSsl: false, + user: '', + password: '', + bypassFilter: '', + bypassLocalAddresses: true, + }, + }, }; if (initialSettings) { this.data = merge(this.data, initialSettings); @@ -622,6 +628,14 @@ class Settings { this.data.jobs = data; } + get network(): NetworkSettings { + return this.data.network; + } + + set network(data: NetworkSettings) { + this.data.network = data; + } + get clientId(): string { return this.data.clientId; } diff --git a/server/lib/settings/migrations/0005_migrate_network_settings.ts b/server/lib/settings/migrations/0005_migrate_network_settings.ts new file mode 100644 index 000000000..a6ad48445 --- /dev/null +++ b/server/lib/settings/migrations/0005_migrate_network_settings.ts @@ -0,0 +1,33 @@ +import type { AllSettings } from '@server/lib/settings'; + +const migrateNetworkSettings = (settings: any): AllSettings => { + if (settings.network) { + return settings; + } + const newSettings = { ...settings }; + newSettings.network = { + ...settings.network, + csrfProtection: settings.main.csrfProtection ?? false, + trustProxy: settings.main.trustProxy ?? false, + forceIpv4First: settings.main.forceIpv4First ?? false, + dnsServers: settings.main.dnsServers ?? '', + proxy: settings.main.proxy ?? { + enabled: false, + hostname: '', + port: 8080, + useSsl: false, + user: '', + password: '', + bypassFilter: '', + bypassLocalAddresses: true, + }, + }; + delete settings.main.csrfProtection; + delete settings.main.trustProxy; + delete settings.main.forceIpv4First; + delete settings.main.dnsServers; + delete settings.main.proxy; + return newSettings; +}; + +export default migrateNetworkSettings; diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index bc8c5ef7c..018f7b46e 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -78,6 +78,21 @@ settingsRoutes.post('/main', async (req, res) => { return res.status(200).json(settings.main); }); +settingsRoutes.get('/network', (req, res) => { + const settings = getSettings(); + + res.status(200).json(settings.network); +}); + +settingsRoutes.post('/network', async (req, res) => { + const settings = getSettings(); + + settings.network = merge(settings.network, req.body); + await settings.save(); + + return res.status(200).json(settings.network); +}); + settingsRoutes.post('/main/regenerate', async (req, res, next) => { const settings = getSettings(); diff --git a/server/utils/restartFlag.ts b/server/utils/restartFlag.ts index 6b364d1f0..24282a091 100644 --- a/server/utils/restartFlag.ts +++ b/server/utils/restartFlag.ts @@ -1,22 +1,25 @@ -import type { MainSettings } from '@server/lib/settings'; +import type { AllSettings, NetworkSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; class RestartFlag { - private settings: MainSettings; + private networkSettings: NetworkSettings; - public initializeSettings(settings: MainSettings): void { - this.settings = { ...settings }; + public initializeSettings(settings: AllSettings): void { + this.networkSettings = { + ...settings.network, + proxy: { ...settings.network.proxy }, + }; } public isSet(): boolean { - const settings = getSettings().main; + const networkSettings = getSettings().network; return ( - this.settings.csrfProtection !== settings.csrfProtection || - this.settings.trustProxy !== settings.trustProxy || - this.settings.proxy.enabled !== settings.proxy.enabled || - this.settings.forceIpv4First !== settings.forceIpv4First || - this.settings.dnsServers !== settings.dnsServers + this.networkSettings.csrfProtection !== networkSettings.csrfProtection || + this.networkSettings.trustProxy !== networkSettings.trustProxy || + this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled || + this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First || + this.networkSettings.dnsServers !== networkSettings.dnsServers ); } } diff --git a/src/components/Settings/SettingsNetwork/index.tsx b/src/components/Settings/SettingsNetwork/index.tsx index a27e948d7..82701cdac 100644 --- a/src/components/Settings/SettingsNetwork/index.tsx +++ b/src/components/Settings/SettingsNetwork/index.tsx @@ -6,7 +6,7 @@ import SettingsBadge from '@app/components/Settings/SettingsBadge'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; -import type { MainSettings } from '@server/lib/settings'; +import type { NetworkSettings } from '@server/lib/settings'; import { Field, Form, Formik } from 'formik'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; @@ -53,7 +53,7 @@ const SettingsMain = () => { data, error, mutate: revalidate, - } = useSWR('/api/v1/settings/main'); + } = useSWR('/api/v1/settings/network'); const NetworkSettingsSchema = Yup.object().shape({ proxyPort: Yup.number().when('proxyEnabled', { @@ -104,7 +104,7 @@ const SettingsMain = () => { validationSchema={NetworkSettingsSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/main', { + const res = await fetch('/api/v1/settings/network', { method: 'POST', headers: { 'Content-Type': 'application/json',