mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
243 lines
7.3 KiB
TypeScript
243 lines
7.3 KiB
TypeScript
import { Button, Modal, Spin } from 'antd';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { type Database, DatabaseType, databaseApi } from '../../../../entity/databases';
|
|
|
|
interface Props {
|
|
database: Database;
|
|
onReadOnlyUserUpdated: (database: Database) => void;
|
|
|
|
onGoBack: () => void;
|
|
onContinue: () => void;
|
|
}
|
|
|
|
const PRIVILEGES_TRUNCATE_LENGTH = 50;
|
|
|
|
export const CreateReadOnlyComponent = ({
|
|
database,
|
|
onReadOnlyUserUpdated,
|
|
onGoBack,
|
|
onContinue,
|
|
}: Props) => {
|
|
const [isCheckingReadOnlyUser, setIsCheckingReadOnlyUser] = useState(false);
|
|
const [isCreatingReadOnlyUser, setIsCreatingReadOnlyUser] = useState(false);
|
|
const [isShowSkipConfirmation, setShowSkipConfirmation] = useState(false);
|
|
const [privileges, setPrivileges] = useState<string[]>([]);
|
|
const [isPrivilegesExpanded, setIsPrivilegesExpanded] = useState(false);
|
|
|
|
const isPostgres = database.type === DatabaseType.POSTGRES;
|
|
const isMysql = database.type === DatabaseType.MYSQL;
|
|
const isMariadb = database.type === DatabaseType.MARIADB;
|
|
const isMongodb = database.type === DatabaseType.MONGODB;
|
|
const databaseTypeName = isPostgres
|
|
? 'PostgreSQL'
|
|
: isMysql
|
|
? 'MySQL'
|
|
: isMariadb
|
|
? 'MariaDB'
|
|
: isMongodb
|
|
? 'MongoDB'
|
|
: 'database';
|
|
|
|
const privilegesLabel = isMongodb ? 'roles' : 'privileges';
|
|
|
|
const checkReadOnlyUser = async (): Promise<boolean> => {
|
|
try {
|
|
const response = await databaseApi.isUserReadOnly(database);
|
|
setPrivileges(response.privileges || []);
|
|
return response.isReadOnly;
|
|
} catch (e) {
|
|
alert((e as Error).message);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const getPrivilegesDisplay = () => {
|
|
const fullText = privileges.join(', ');
|
|
if (isPrivilegesExpanded || fullText.length <= PRIVILEGES_TRUNCATE_LENGTH) {
|
|
return fullText;
|
|
}
|
|
|
|
return fullText.substring(0, PRIVILEGES_TRUNCATE_LENGTH) + '...';
|
|
};
|
|
|
|
const shouldShowExpandToggle = () => {
|
|
const fullText = privileges.join(', ');
|
|
return fullText.length > PRIVILEGES_TRUNCATE_LENGTH;
|
|
};
|
|
|
|
const createReadOnlyUser = async () => {
|
|
setIsCreatingReadOnlyUser(true);
|
|
|
|
try {
|
|
const response = await databaseApi.createReadOnlyUser(database);
|
|
|
|
if (isPostgres && database.postgresql) {
|
|
database.postgresql.username = response.username;
|
|
database.postgresql.password = response.password;
|
|
} else if (isMysql && database.mysql) {
|
|
database.mysql.username = response.username;
|
|
database.mysql.password = response.password;
|
|
} else if (isMariadb && database.mariadb) {
|
|
database.mariadb.username = response.username;
|
|
database.mariadb.password = response.password;
|
|
} else if (isMongodb && database.mongodb) {
|
|
database.mongodb.username = response.username;
|
|
database.mongodb.password = response.password;
|
|
}
|
|
|
|
onReadOnlyUserUpdated(database);
|
|
onContinue();
|
|
} catch (e) {
|
|
alert((e as Error).message);
|
|
}
|
|
|
|
setIsCreatingReadOnlyUser(false);
|
|
};
|
|
|
|
const handleSkip = () => {
|
|
setShowSkipConfirmation(true);
|
|
};
|
|
|
|
const handleSkipConfirmed = () => {
|
|
setShowSkipConfirmation(false);
|
|
onContinue();
|
|
};
|
|
|
|
useEffect(() => {
|
|
const run = async () => {
|
|
setIsCheckingReadOnlyUser(true);
|
|
|
|
const isReadOnly = await checkReadOnlyUser();
|
|
if (isReadOnly) {
|
|
onContinue();
|
|
}
|
|
|
|
setIsCheckingReadOnlyUser(false);
|
|
};
|
|
run();
|
|
}, []);
|
|
|
|
if (isCheckingReadOnlyUser) {
|
|
return (
|
|
<div className="flex items-center">
|
|
<Spin />
|
|
<span className="ml-3">Checking read-only user...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-5">
|
|
<p className="mb-3 text-lg font-bold">Create a read-only user for Databasus?</p>
|
|
|
|
<p className="mb-2">
|
|
A read-only user is a {databaseTypeName} user with limited permissions that can only read
|
|
data from your database, not modify it. This is recommended for backup operations because:
|
|
</p>
|
|
|
|
<ul className="mb-2 ml-5 list-disc">
|
|
<li>it prevents accidental data modifications during backup</li>
|
|
<li>it follows the principle of least privilege</li>
|
|
<li>it's a security best practice</li>
|
|
</ul>
|
|
|
|
<p className="mb-2">
|
|
Databasus enforce enterprise-grade security (
|
|
<a
|
|
href="https://databasus.com/security"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="!text-blue-600 dark:!text-blue-400"
|
|
>
|
|
read in details here
|
|
</a>
|
|
). However, it is not possible to be covered from all possible risks.
|
|
</p>
|
|
|
|
<p className="mt-3">
|
|
<b>Read-only user allows to avoid storing credentials with write access at all</b>. Even
|
|
in the worst case of hacking, nobody will be able to corrupt your data.
|
|
</p>
|
|
|
|
<p className="mt-3">
|
|
{privileges.length === 0 ? (
|
|
<>
|
|
Current user has <b>no write {privilegesLabel}</b>.
|
|
</>
|
|
) : (
|
|
<>
|
|
Current user has the following write {privilegesLabel}:{' '}
|
|
<span
|
|
className={shouldShowExpandToggle() ? 'cursor-pointer hover:opacity-80' : ''}
|
|
onClick={() =>
|
|
shouldShowExpandToggle() && setIsPrivilegesExpanded(!isPrivilegesExpanded)
|
|
}
|
|
>
|
|
{getPrivilegesDisplay()}
|
|
{shouldShowExpandToggle() && (
|
|
<span className="ml-1 text-xs text-blue-600 hover:opacity-80">
|
|
({isPrivilegesExpanded ? 'collapse' : 'expand'})
|
|
</span>
|
|
)}
|
|
</span>
|
|
</>
|
|
)}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mt-5 flex">
|
|
<Button className="mr-auto" type="primary" ghost onClick={() => onGoBack()}>
|
|
Back
|
|
</Button>
|
|
|
|
<Button className="mr-2 ml-auto" danger ghost onClick={handleSkip}>
|
|
Skip
|
|
</Button>
|
|
|
|
<Button
|
|
type="primary"
|
|
onClick={createReadOnlyUser}
|
|
loading={isCreatingReadOnlyUser}
|
|
disabled={isCreatingReadOnlyUser}
|
|
>
|
|
Yes, create read-only user
|
|
</Button>
|
|
</div>
|
|
|
|
<Modal
|
|
title="Skip read-only user creation?"
|
|
open={isShowSkipConfirmation}
|
|
onCancel={() => setShowSkipConfirmation(false)}
|
|
footer={null}
|
|
width={450}
|
|
>
|
|
<div className="mb-5">
|
|
<p className="mb-2">Are you sure you want to skip creating a read-only user?</p>
|
|
|
|
<p className="mb-2">
|
|
Using a user with full permissions for backups is not recommended and may pose security
|
|
risks. Databasus is highly recommending you to not skip this step.
|
|
</p>
|
|
|
|
<p>
|
|
100% protection is never possible. It's better to be safe in case of 0.01% risk of
|
|
full hacking. So it is better to follow the secure way with read-only user.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex justify-end">
|
|
<Button className="mr-2" danger onClick={handleSkipConfirmed}>
|
|
Yes, I accept risks
|
|
</Button>
|
|
|
|
<Button type="primary" onClick={() => setShowSkipConfirmation(false)}>
|
|
Let's continue with the secure way
|
|
</Button>
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|