mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat: api key regeneration
This commit is contained in:
@@ -1028,6 +1028,19 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/MainSettings'
|
$ref: '#/components/schemas/MainSettings'
|
||||||
|
/settings/main/regenerate:
|
||||||
|
get:
|
||||||
|
summary: Returns main settings with newly generated API Key
|
||||||
|
description: Retreives all main settings in JSON format with new API Key
|
||||||
|
tags:
|
||||||
|
- settings
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MainSettings'
|
||||||
/settings/plex:
|
/settings/plex:
|
||||||
get:
|
get:
|
||||||
summary: Returns plex settings
|
summary: Returns plex settings
|
||||||
|
@@ -213,7 +213,7 @@ class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private generateApiKey(): string {
|
private generateApiKey(): string {
|
||||||
return Buffer.from(`${Date.now()}${this.clientId}`).toString('base64');
|
return Buffer.from(`${Date.now()}${uuidv4()})`).toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -23,16 +23,27 @@ import { getAppVersion } from '../utils/appVersion';
|
|||||||
|
|
||||||
const settingsRoutes = Router();
|
const settingsRoutes = Router();
|
||||||
|
|
||||||
settingsRoutes.get('/main', (req, res) => {
|
const filteredMainSettings = (
|
||||||
const settings = getSettings();
|
user: User,
|
||||||
|
main: MainSettings
|
||||||
if (!req.user?.hasPermission(Permission.ADMIN)) {
|
): Partial<MainSettings> => {
|
||||||
return res.status(200).json({
|
if (!user?.hasPermission(Permission.ADMIN)) {
|
||||||
applicationUrl: settings.main.applicationUrl,
|
return {
|
||||||
} as Partial<MainSettings>);
|
applicationUrl: main.applicationUrl,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json(settings.main);
|
return main;
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsRoutes.get('/main', (req, res, next) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
if (!req.user) {
|
||||||
|
return next({ status: 500, message: 'User missing from request' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(filteredMainSettings(req.user, settings.main));
|
||||||
});
|
});
|
||||||
|
|
||||||
settingsRoutes.post('/main', (req, res) => {
|
settingsRoutes.post('/main', (req, res) => {
|
||||||
@@ -44,6 +55,18 @@ settingsRoutes.post('/main', (req, res) => {
|
|||||||
return res.status(200).json(settings.main);
|
return res.status(200).json(settings.main);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
settingsRoutes.get('/main/regenerate', (req, res, next) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
const main = settings.regenerateApiKey();
|
||||||
|
|
||||||
|
if (!req.user) {
|
||||||
|
return next({ status: 500, message: 'User missing from request' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(filteredMainSettings(req.user, main));
|
||||||
|
});
|
||||||
|
|
||||||
settingsRoutes.get('/plex', (_req, res) => {
|
settingsRoutes.get('/plex', (_req, res) => {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
|
|
||||||
|
@@ -25,7 +25,10 @@ const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={setCopied}
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setCopied();
|
||||||
|
}}
|
||||||
className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
@@ -8,6 +8,7 @@ import axios from 'axios';
|
|||||||
import Button from '../Common/Button';
|
import Button from '../Common/Button';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useUser, Permission } from '../../hooks/useUser';
|
import { useUser, Permission } from '../../hooks/useUser';
|
||||||
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
generalsettings: 'General Settings',
|
generalsettings: 'General Settings',
|
||||||
@@ -17,15 +18,37 @@ const messages = defineMessages({
|
|||||||
saving: 'Saving...',
|
saving: 'Saving...',
|
||||||
apikey: 'API Key',
|
apikey: 'API Key',
|
||||||
applicationurl: 'Application URL',
|
applicationurl: 'Application URL',
|
||||||
|
toastApiKeySuccess: 'New API Key generated!',
|
||||||
|
toastApiKeyFailure: 'Something went wrong generating a new API Key.',
|
||||||
|
toastSettingsSuccess: 'Settings saved.',
|
||||||
|
toastSettingsFailure: 'Something went wrong saving settings.',
|
||||||
});
|
});
|
||||||
|
|
||||||
const SettingsMain: React.FC = () => {
|
const SettingsMain: React.FC = () => {
|
||||||
|
const { addToast } = useToasts();
|
||||||
const { hasPermission } = useUser();
|
const { hasPermission } = useUser();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { data, error, revalidate } = useSWR<MainSettings>(
|
const { data, error, revalidate } = useSWR<MainSettings>(
|
||||||
'/api/v1/settings/main'
|
'/api/v1/settings/main'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const regenerate = async () => {
|
||||||
|
try {
|
||||||
|
await axios.get('/api/v1/settings/main/regenerate');
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
addToast(intl.formatMessage(messages.toastApiKeySuccess), {
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'success',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
addToast(intl.formatMessage(messages.toastApiKeyFailure), {
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!data && !error) {
|
if (!data && !error) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />;
|
||||||
}
|
}
|
||||||
@@ -50,8 +73,16 @@ const SettingsMain: React.FC = () => {
|
|||||||
await axios.post('/api/v1/settings/main', {
|
await axios.post('/api/v1/settings/main', {
|
||||||
applicationUrl: values.applicationUrl,
|
applicationUrl: values.applicationUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'success',
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO show error
|
addToast(intl.formatMessage(messages.toastSettingsFailure), {
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'error',
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
revalidate();
|
revalidate();
|
||||||
}
|
}
|
||||||
@@ -77,8 +108,17 @@ const SettingsMain: React.FC = () => {
|
|||||||
value={data?.apiKey}
|
value={data?.apiKey}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<CopyButton textToCopy={data?.apiKey ?? ''} />
|
<CopyButton
|
||||||
<button className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
textToCopy={data?.apiKey ?? ''}
|
||||||
|
key={data?.apiKey}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
regenerate();
|
||||||
|
}}
|
||||||
|
className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5"
|
className="w-5 h-5"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
@@ -242,6 +242,10 @@
|
|||||||
"components.Settings.startscan": "Start Scan",
|
"components.Settings.startscan": "Start Scan",
|
||||||
"components.Settings.sync": "Sync Plex Libraries",
|
"components.Settings.sync": "Sync Plex Libraries",
|
||||||
"components.Settings.syncing": "Syncing…",
|
"components.Settings.syncing": "Syncing…",
|
||||||
|
"components.Settings.toastApiKeyFailure": "Something went wrong generating a new API Key.",
|
||||||
|
"components.Settings.toastApiKeySuccess": "New API Key generated!",
|
||||||
|
"components.Settings.toastSettingsFailure": "Something went wrong saving settings.",
|
||||||
|
"components.Settings.toastSettingsSuccess": "Settings saved.",
|
||||||
"components.Settings.validationHostnameRequired": "You must provide a hostname/IP",
|
"components.Settings.validationHostnameRequired": "You must provide a hostname/IP",
|
||||||
"components.Settings.validationPortRequired": "You must provide a port",
|
"components.Settings.validationPortRequired": "You must provide a port",
|
||||||
"components.Setup.configureplex": "Configure Plex",
|
"components.Setup.configureplex": "Configure Plex",
|
||||||
|
Reference in New Issue
Block a user