feat: api key regeneration

pull/371/head
sct 4 years ago
parent 6933b661ca
commit 6beac736ef

@ -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 = (
user: User,
main: MainSettings
): Partial<MainSettings> => {
if (!user?.hasPermission(Permission.ADMIN)) {
return {
applicationUrl: main.applicationUrl,
};
}
return main;
};
settingsRoutes.get('/main', (req, res, next) => {
const settings = getSettings(); const settings = getSettings();
if (!req.user?.hasPermission(Permission.ADMIN)) { if (!req.user) {
return res.status(200).json({ return next({ status: 500, message: 'User missing from request' });
applicationUrl: settings.main.applicationUrl,
} as Partial<MainSettings>);
} }
res.status(200).json(settings.main); 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",

Loading…
Cancel
Save