feat: generate real api key

This also hides the api key from users without the ADMIN permission. It will not be returned from
the api for them. Regenerate functionality is not in the commit.
This commit is contained in:
sct
2020-12-08 05:04:15 +00:00
parent 20769d58dd
commit a8393707fe
8 changed files with 64 additions and 40 deletions

View File

@@ -102,7 +102,7 @@ class Settings {
this.data = {
clientId: '',
main: {
apiKey: 'temp',
apiKey: '',
applicationUrl: '',
},
plex: {
@@ -144,6 +144,10 @@ class Settings {
}
get main(): MainSettings {
if (!this.data.main.apiKey) {
this.data.main.apiKey = this.generateApiKey();
this.save();
}
return this.data.main;
}
@@ -200,6 +204,16 @@ class Settings {
return this.data.clientId;
}
public regenerateApiKey(): MainSettings {
this.main.apiKey = this.generateApiKey();
this.save();
return this.main;
}
private generateApiKey(): string {
return Buffer.from(`${Date.now()}${this.clientId}`).toString('base64');
}
/**
* Settings Load
*

View File

@@ -4,6 +4,7 @@ import {
RadarrSettings,
SonarrSettings,
Library,
MainSettings,
} from '../lib/settings';
import { getRepository } from 'typeorm';
import { User } from '../entity/User';
@@ -19,9 +20,15 @@ import { merge } from 'lodash';
const settingsRoutes = Router();
settingsRoutes.get('/main', (_req, res) => {
settingsRoutes.get('/main', (req, res) => {
const settings = getSettings();
if (!req.user?.hasPermission(Permission.ADMIN)) {
return res.status(200).json({
applicationUrl: settings.main.applicationUrl,
} as Partial<MainSettings>);
}
res.status(200).json(settings.main);
});

View File

@@ -7,6 +7,7 @@ import { Form, Formik, Field } from 'formik';
import axios from 'axios';
import Button from '../Common/Button';
import { defineMessages, useIntl } from 'react-intl';
import { useUser, Permission } from '../../hooks/useUser';
const messages = defineMessages({
generalsettings: 'General Settings',
@@ -19,6 +20,7 @@ const messages = defineMessages({
});
const SettingsMain: React.FC = () => {
const { hasPermission } = useUser();
const intl = useIntl();
const { data, error, revalidate } = useSWR<MainSettings>(
'/api/v1/settings/main'
@@ -41,7 +43,6 @@ const SettingsMain: React.FC = () => {
<div className="mt-6 sm:mt-5">
<Formik
initialValues={{
apiKey: data?.apiKey,
applicationUrl: data?.applicationUrl,
}}
onSubmit={async (values) => {
@@ -59,40 +60,42 @@ const SettingsMain: React.FC = () => {
{({ isSubmitting }) => {
return (
<Form>
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
<label
htmlFor="username"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
>
{intl.formatMessage(messages.apikey)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm">
<input
type="text"
id="username"
className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
value={data?.apiKey}
readOnly
/>
<CopyButton textToCopy={data?.apiKey ?? ''} />
<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">
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
</button>
{hasPermission(Permission.ADMIN) && (
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
<label
htmlFor="username"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
>
{intl.formatMessage(messages.apikey)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm">
<input
type="text"
id="apiKey"
className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
value={data?.apiKey}
readOnly
/>
<CopyButton textToCopy={data?.apiKey ?? ''} />
<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">
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
</button>
</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 sm:pt-5">
<label
htmlFor="name"

View File

@@ -6,7 +6,7 @@ import useRouteGuard from '../../hooks/useRouteGuard';
import { Permission } from '../../hooks/useUser';
const SettingsPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS);
useRouteGuard(Permission.MANAGE_SETTINGS);
return (
<SettingsLayout>
<SettingsMain />

View File

@@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard';
const SettingsMainPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS);
useRouteGuard(Permission.MANAGE_SETTINGS);
return (
<SettingsLayout>
<SettingsJobs />

View File

@@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard';
const SettingsMainPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS);
useRouteGuard(Permission.MANAGE_SETTINGS);
return (
<SettingsLayout>
<SettingsMain />

View File

@@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard';
const PlexSettingsPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS);
useRouteGuard(Permission.MANAGE_SETTINGS);
return (
<SettingsLayout>
<SettingsPlex />

View File

@@ -6,7 +6,7 @@ import { Permission } from '../../hooks/useUser';
import useRouteGuard from '../../hooks/useRouteGuard';
const ServicesSettingsPage: NextPage = () => {
useRouteGuard(Permission.MANAGE_USERS);
useRouteGuard(Permission.MANAGE_SETTINGS);
return (
<SettingsLayout>
<SettingsServices />