encrypted config handling
Signed-off-by: FTCHD <144691102+FTCHD@users.noreply.github.com>
This commit is contained in:
+93
-52
@@ -1,8 +1,8 @@
|
||||
import * as Sentry from '@sentry/browser'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { BaseDirectory, appLocalDataDir, sep } from '@tauri-apps/api/path'
|
||||
import { BaseDirectory, appLocalDataDir, appLogDir, sep } from '@tauri-apps/api/path'
|
||||
import { tempDir } from '@tauri-apps/api/path'
|
||||
import { ask, message } from '@tauri-apps/plugin-dialog'
|
||||
import { message } from '@tauri-apps/plugin-dialog'
|
||||
import { copyFile, exists, mkdir, readTextFile, remove } from '@tauri-apps/plugin-fs'
|
||||
import { writeFile } from '@tauri-apps/plugin-fs'
|
||||
import { fetch } from '@tauri-apps/plugin-http'
|
||||
@@ -49,43 +49,6 @@ export async function initRclone(args: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (configFiles.length === 0) {
|
||||
if (system) {
|
||||
let isEncrypted = false
|
||||
|
||||
// Detect if the config is encrypted
|
||||
try {
|
||||
const configContent = await readTextFile(defaultPath)
|
||||
isEncrypted = configContent.includes('RCLONE_ENCRYPT_V0:')
|
||||
} catch (error) {
|
||||
console.log('[initRclone] could not read config file, asking user:', error)
|
||||
isEncrypted = await ask(
|
||||
'Is your configuration encrypted? Press "No" if you\'re unsure or using the default config file.',
|
||||
{
|
||||
title: 'Config file found',
|
||||
kind: 'info',
|
||||
okLabel: 'Yes',
|
||||
cancelLabel: 'No',
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (isEncrypted) {
|
||||
await ask(
|
||||
'Encrypted config files cannot be imported during the initial setup. Use a blank conf file and import the encrypted configuration later in Settings.',
|
||||
{
|
||||
title: 'Not supported yet',
|
||||
kind: 'error',
|
||||
okLabel: 'OK',
|
||||
cancelLabel: '',
|
||||
}
|
||||
)
|
||||
await exit(0)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configFiles = configFiles.filter((config) => config.id !== 'default')
|
||||
configFiles.unshift({
|
||||
id: 'default',
|
||||
@@ -113,6 +76,8 @@ export async function initRclone(args: string[]) {
|
||||
''
|
||||
)
|
||||
|
||||
console.log('[initRclone] configFolderPath', configFolderPath)
|
||||
|
||||
if (activeConfigFile.sync) {
|
||||
if (!(await exists(configFolderPath + sep() + 'rclone.conf'))) {
|
||||
await message('The config file could not be found. Switching to the default config.', {
|
||||
@@ -120,6 +85,7 @@ export async function initRclone(args: string[]) {
|
||||
kind: 'error',
|
||||
okLabel: 'OK',
|
||||
})
|
||||
activeConfigFile = configFiles[0]
|
||||
configFolderPath = (await getConfigPath({ id: 'default', validate: true })).replace(
|
||||
/\/rclone\.conf$/,
|
||||
''
|
||||
@@ -128,19 +94,71 @@ export async function initRclone(args: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
const extraParams =
|
||||
activeConfigFile.id === 'default'
|
||||
? undefined
|
||||
: {
|
||||
env: {
|
||||
...(activeConfigFile.isEncrypted
|
||||
? activeConfigFile.passCommand
|
||||
? { RCLONE_CONFIG_PASS_COMMAND: activeConfigFile.passCommand }
|
||||
: { RCLONE_CONFIG_PASS: activeConfigFile.pass! }
|
||||
: {}),
|
||||
RCLONE_CONFIG_DIR: configFolderPath,
|
||||
},
|
||||
}
|
||||
let password: string | null = activeConfigFile.pass || activeConfigFile.passCommand || null
|
||||
try {
|
||||
const configContent = await readTextFile(`${configFolderPath}${sep()}rclone.conf`)
|
||||
const isEncrypted = configContent.includes('RCLONE_ENCRYPT_V0:')
|
||||
if (isEncrypted && !password) {
|
||||
password = await promptForConfigPassword(activeConfigFile.label)
|
||||
console.log('[initRclone] password', password)
|
||||
|
||||
if (!password) {
|
||||
await message('Password is required for encrypted configurations.', {
|
||||
title: 'Password Required',
|
||||
kind: 'error',
|
||||
okLabel: 'OK',
|
||||
})
|
||||
await exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
if (!activeConfigFile.isEncrypted) {
|
||||
const updatedConfigFile = { ...activeConfigFile, isEncrypted: true }
|
||||
const updatedConfigFiles = configFiles.map((config) =>
|
||||
config.id === activeConfigFile!.id ? updatedConfigFile : config
|
||||
)
|
||||
usePersistedStore.setState({
|
||||
configFiles: updatedConfigFiles,
|
||||
activeConfigFile: updatedConfigFile,
|
||||
})
|
||||
|
||||
// Update activeConfigFile reference for the rest of the function
|
||||
activeConfigFile = updatedConfigFile
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[initRclone] could not read config file', error)
|
||||
const appLogDirPath = await appLogDir()
|
||||
await message(
|
||||
'Could not read config file, please file an issue on GitHub.\n\nLogs: ' + appLogDirPath,
|
||||
{
|
||||
title: 'Error',
|
||||
kind: 'error',
|
||||
okLabel: 'OK',
|
||||
}
|
||||
)
|
||||
await exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
const extraParams: { env: Record<string, string> } = {
|
||||
env: {},
|
||||
}
|
||||
|
||||
if (activeConfigFile.isEncrypted) {
|
||||
extraParams.env.RCLONE_ASK_PASSWORD = 'false'
|
||||
if (activeConfigFile.passCommand) {
|
||||
extraParams.env.RCLONE_CONFIG_PASS_COMMAND = activeConfigFile.passCommand
|
||||
} else {
|
||||
extraParams.env.RCLONE_CONFIG_PASS = activeConfigFile.pass || password!
|
||||
}
|
||||
}
|
||||
|
||||
if (activeConfigFile.id !== 'default') {
|
||||
extraParams.env.RCLONE_CONFIG_DIR = configFolderPath
|
||||
}
|
||||
|
||||
console.log('[initRclone] extraParams', extraParams)
|
||||
|
||||
if (system) {
|
||||
console.log('[initRclone] running system rclone')
|
||||
@@ -301,3 +319,26 @@ export async function provisionRclone() {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for a password for an encrypted configuration
|
||||
* @param configLabel - The label of the configuration file
|
||||
* @returns Promise<string | null> - The password entered by the user, or null if cancelled
|
||||
*/
|
||||
async function promptForConfigPassword(configLabel: string): Promise<string | null> {
|
||||
try {
|
||||
const result = (await invoke('prompt_password', {
|
||||
title: 'Rclone UI',
|
||||
message: `Please enter the password for the encrypted configuration "${configLabel}".`,
|
||||
})) as string | null
|
||||
|
||||
if (typeof result === 'string') {
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('[promptForConfigPassword] Error prompting for password:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
+144
-1
@@ -69,6 +69,149 @@ fn get_uid() -> String {
|
||||
return machine_uid::get().unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn prompt_password(title: String, message: String) -> Result<Option<String>, String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use std::process::Command;
|
||||
|
||||
let script = format!(
|
||||
r#"display dialog "{}" with title "{}" default answer "" with hidden answer"#,
|
||||
message.replace("\"", "\\\""),
|
||||
title.replace("\"", "\\\"")
|
||||
);
|
||||
|
||||
let output = Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(&script)
|
||||
.output()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if output.status.success() {
|
||||
let result = String::from_utf8_lossy(&output.stdout);
|
||||
// Parse AppleScript result: "text returned:password, button returned:OK"
|
||||
if let Some(password_part) = result.split("text returned:").nth(1) {
|
||||
if let Some(password) = password_part.split(", button returned:").next() {
|
||||
return Ok(Some(password.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::process::Command;
|
||||
|
||||
// Use PowerShell to create a credential dialog on Windows
|
||||
let powershell_script = format!(
|
||||
r#"
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.Text = '{}'
|
||||
$form.Size = New-Object System.Drawing.Size(350, 200)
|
||||
$form.StartPosition = 'CenterScreen'
|
||||
$form.FormBorderStyle = 'FixedDialog'
|
||||
$form.MaximizeBox = $false
|
||||
$form.MinimizeBox = $false
|
||||
$form.TopMost = $true
|
||||
|
||||
$label = New-Object System.Windows.Forms.Label
|
||||
$label.Location = New-Object System.Drawing.Point(10, 20)
|
||||
$label.Size = New-Object System.Drawing.Size(320, 40)
|
||||
$label.Text = '{}'
|
||||
$form.Controls.Add($label)
|
||||
|
||||
$textBox = New-Object System.Windows.Forms.TextBox
|
||||
$textBox.Location = New-Object System.Drawing.Point(10, 70)
|
||||
$textBox.Size = New-Object System.Drawing.Size(320, 20)
|
||||
$textBox.UseSystemPasswordChar = $true
|
||||
$form.Controls.Add($textBox)
|
||||
|
||||
$okButton = New-Object System.Windows.Forms.Button
|
||||
$okButton.Location = New-Object System.Drawing.Point(175, 110)
|
||||
$okButton.Size = New-Object System.Drawing.Size(75, 23)
|
||||
$okButton.Text = 'OK'
|
||||
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
|
||||
$form.AcceptButton = $okButton
|
||||
$form.Controls.Add($okButton)
|
||||
|
||||
$cancelButton = New-Object System.Windows.Forms.Button
|
||||
$cancelButton.Location = New-Object System.Drawing.Point(255, 110)
|
||||
$cancelButton.Size = New-Object System.Drawing.Size(75, 23)
|
||||
$cancelButton.Text = 'Cancel'
|
||||
$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
|
||||
$form.CancelButton = $cancelButton
|
||||
$form.Controls.Add($cancelButton)
|
||||
|
||||
$form.Add_Shown({{$textBox.Select()}})
|
||||
$result = $form.ShowDialog()
|
||||
|
||||
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {{
|
||||
$textBox.Text
|
||||
}}
|
||||
"#,
|
||||
title.replace("'", "''"),
|
||||
message.replace("'", "''")
|
||||
);
|
||||
|
||||
let output = Command::new("powershell")
|
||||
.args(&["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &powershell_script])
|
||||
.output()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if output.status.success() {
|
||||
let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !result.is_empty() {
|
||||
return Ok(Some(result));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Try different methods for Linux
|
||||
|
||||
// First try zenity (most common)
|
||||
if let Ok(output) = std::process::Command::new("zenity")
|
||||
.args(&["--password", "--title", &title, "--text", &message])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() {
|
||||
let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !result.is_empty() {
|
||||
return Ok(Some(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to kdialog (KDE)
|
||||
if let Ok(output) = std::process::Command::new("kdialog")
|
||||
.args(&["--password", &message, "--title", &title])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() {
|
||||
let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !result.is_empty() {
|
||||
return Ok(Some(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("No suitable password dialog found. Please install zenity or kdialog.".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
{
|
||||
Err("Password input not supported on this platform".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let client = sentry::init((
|
||||
@@ -106,7 +249,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_log::Builder::new().build())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![unzip_file, get_arch, get_uid])
|
||||
.invoke_handler(tauri::generate_handler![unzip_file, get_arch, get_uid, prompt_password])
|
||||
.setup(|_app| Ok(()))
|
||||
// .setup(|app| {
|
||||
// if cfg!(debug_assertions) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
Input,
|
||||
Switch,
|
||||
Textarea,
|
||||
} from '@heroui/react'
|
||||
import { Button } from '@heroui/react'
|
||||
@@ -28,6 +29,8 @@ export default function ConfigCreateDrawer({
|
||||
label: 'New Config',
|
||||
})
|
||||
const [configContent, setConfigContent] = useState<string | null>(null)
|
||||
|
||||
const [isPasswordCommand, setIsPasswordCommand] = useState(false)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const isEncrypted = useMemo(() => {
|
||||
@@ -38,8 +41,18 @@ export default function ConfigCreateDrawer({
|
||||
async ({
|
||||
label,
|
||||
pass,
|
||||
passCommand,
|
||||
content,
|
||||
}: { label?: string; pass?: string; content: string | null }) => {
|
||||
isPasswordCommand,
|
||||
isEncrypted,
|
||||
}: {
|
||||
label?: string
|
||||
pass?: string
|
||||
passCommand?: string
|
||||
content: string | null
|
||||
isPasswordCommand: boolean
|
||||
isEncrypted: boolean
|
||||
}) => {
|
||||
try {
|
||||
if (!label) {
|
||||
throw new Error('Label is required')
|
||||
@@ -49,8 +62,8 @@ export default function ConfigCreateDrawer({
|
||||
throw new Error('Content is required')
|
||||
}
|
||||
|
||||
if (!pass && content.includes('RCLONE_ENCRYPT_V0:')) {
|
||||
throw new Error('Password is required for encrypted configs')
|
||||
if (isEncrypted && isPasswordCommand && !passCommand) {
|
||||
throw new Error('Password command is required for encrypted configs')
|
||||
}
|
||||
|
||||
setIsSaving(true)
|
||||
@@ -68,10 +81,10 @@ export default function ConfigCreateDrawer({
|
||||
usePersistedStore.getState().addConfigFile({
|
||||
id: generatedId,
|
||||
label,
|
||||
pass,
|
||||
isEncrypted: content.includes('RCLONE_ENCRYPT_V0:'),
|
||||
pass: isPasswordCommand ? undefined : pass,
|
||||
passCommand: isPasswordCommand ? passCommand : undefined,
|
||||
isEncrypted: isEncrypted,
|
||||
sync: undefined,
|
||||
passCommand: undefined,
|
||||
})
|
||||
|
||||
onClose()
|
||||
@@ -113,7 +126,11 @@ export default function ConfigCreateDrawer({
|
||||
handleCreate({
|
||||
label: config.label,
|
||||
pass: config.pass,
|
||||
passCommand: config.passCommand,
|
||||
content: configContent,
|
||||
isPasswordCommand: isPasswordCommand,
|
||||
isEncrypted:
|
||||
configContent?.includes('RCLONE_ENCRYPT_V0:') || false,
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -138,20 +155,49 @@ export default function ConfigCreateDrawer({
|
||||
|
||||
{isEncrypted && (
|
||||
<Input
|
||||
label="Password"
|
||||
label={
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p className="text-medium">Password</p>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={isPasswordCommand}
|
||||
onValueChange={() =>
|
||||
setIsPasswordCommand(!isPasswordCommand)
|
||||
}
|
||||
color="primary"
|
||||
>
|
||||
Command
|
||||
</Switch>
|
||||
</div>
|
||||
}
|
||||
labelPlacement="outside"
|
||||
placeholder="Enter the password for your config file"
|
||||
type="password"
|
||||
value={config.pass}
|
||||
autoComplete="off"
|
||||
placeholder={
|
||||
isPasswordCommand
|
||||
? 'Enter the password command for your config file'
|
||||
: 'Leave blank to be prompted on every startup'
|
||||
}
|
||||
type={isPasswordCommand ? 'text' : 'password'}
|
||||
value={isPasswordCommand ? config.passCommand : config.pass}
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
spellCheck="false"
|
||||
onValueChange={(value) => {
|
||||
setConfig({ ...config, pass: value })
|
||||
setConfig({
|
||||
...config,
|
||||
...(isPasswordCommand
|
||||
? { passCommand: value }
|
||||
: { pass: value }),
|
||||
})
|
||||
}}
|
||||
isClearable={true}
|
||||
onClear={() => {
|
||||
setConfig({ ...config, pass: '' })
|
||||
setConfig({
|
||||
...config,
|
||||
...(isPasswordCommand
|
||||
? { passCommand: '' }
|
||||
: { pass: '' }),
|
||||
})
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
Input,
|
||||
Switch,
|
||||
Textarea,
|
||||
} from '@heroui/react'
|
||||
import { Button } from '@heroui/react'
|
||||
@@ -28,8 +29,10 @@ export default function ConfigEditDrawer({
|
||||
|
||||
const [configLabel, setConfigLabel] = useState<string | null>(null)
|
||||
const [configPass, setConfigPass] = useState<string | null>(null)
|
||||
const [configPassCommand, setConfigPassCommand] = useState<string | null>(null)
|
||||
const [configContent, setConfigContent] = useState<string | null>(null)
|
||||
|
||||
const [isPasswordCommand, setIsPasswordCommand] = useState(false)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const isEncrypted = useMemo(() => {
|
||||
@@ -37,7 +40,21 @@ export default function ConfigEditDrawer({
|
||||
}, [configContent])
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
async ({ label, pass, content }: { label?: string; pass?: string; content?: string }) => {
|
||||
async ({
|
||||
label,
|
||||
pass,
|
||||
content,
|
||||
passCommand,
|
||||
isPasswordCommand,
|
||||
isEncrypted,
|
||||
}: {
|
||||
label?: string
|
||||
pass?: string
|
||||
content?: string
|
||||
passCommand?: string
|
||||
isPasswordCommand?: boolean
|
||||
isEncrypted?: boolean
|
||||
}) => {
|
||||
if (!id) return
|
||||
|
||||
try {
|
||||
@@ -49,8 +66,8 @@ export default function ConfigEditDrawer({
|
||||
throw new Error('Content is required')
|
||||
}
|
||||
|
||||
if (content.includes('RCLONE_ENCRYPT_V0:') && !pass) {
|
||||
throw new Error('Password is required for encrypted configs')
|
||||
if (isEncrypted && isPasswordCommand && !passCommand) {
|
||||
throw new Error('Password command is required for encrypted configs')
|
||||
}
|
||||
|
||||
setIsSaving(true)
|
||||
@@ -60,8 +77,9 @@ export default function ConfigEditDrawer({
|
||||
|
||||
usePersistedStore.getState().updateConfigFile(id, {
|
||||
label,
|
||||
pass: pass || undefined,
|
||||
isEncrypted: content.includes('RCLONE_ENCRYPT_V0:'),
|
||||
pass: isPasswordCommand ? undefined : pass,
|
||||
passCommand: isPasswordCommand ? passCommand : undefined,
|
||||
isEncrypted: isEncrypted,
|
||||
})
|
||||
|
||||
onClose()
|
||||
@@ -92,6 +110,8 @@ export default function ConfigEditDrawer({
|
||||
setConfigContent(text)
|
||||
setConfigLabel(initialConfig.label)
|
||||
setConfigPass(initialConfig.pass || null)
|
||||
setConfigPassCommand(initialConfig.passCommand || null)
|
||||
setIsPasswordCommand(initialConfig.passCommand !== null)
|
||||
}, [initialConfig])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -103,6 +123,8 @@ export default function ConfigEditDrawer({
|
||||
setConfigContent(null)
|
||||
setConfigLabel(null)
|
||||
setConfigPass(null)
|
||||
setConfigPassCommand(null)
|
||||
setIsPasswordCommand(false)
|
||||
}
|
||||
}, [isOpen, initializeConfig, configLabel, configContent])
|
||||
|
||||
@@ -134,6 +156,10 @@ export default function ConfigEditDrawer({
|
||||
label: configLabel || undefined,
|
||||
pass: configPass || undefined,
|
||||
content: configContent || undefined,
|
||||
passCommand: configPassCommand || undefined,
|
||||
isPasswordCommand: isPasswordCommand,
|
||||
isEncrypted:
|
||||
configContent?.includes('RCLONE_ENCRYPT_V0:') || false,
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -159,21 +185,50 @@ export default function ConfigEditDrawer({
|
||||
|
||||
{isEncrypted && (
|
||||
<Input
|
||||
name="label"
|
||||
label="Password"
|
||||
label={
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p className="text-medium">Password</p>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={isPasswordCommand}
|
||||
onValueChange={() =>
|
||||
setIsPasswordCommand(!isPasswordCommand)
|
||||
}
|
||||
color="primary"
|
||||
>
|
||||
Command
|
||||
</Switch>
|
||||
</div>
|
||||
}
|
||||
labelPlacement="outside"
|
||||
placeholder="Enter the password for your config file"
|
||||
type="password"
|
||||
value={configPass || ''}
|
||||
autoComplete="off"
|
||||
placeholder={
|
||||
isPasswordCommand
|
||||
? 'Enter the password command for your config file'
|
||||
: 'Leave blank to be prompted on every startup'
|
||||
}
|
||||
type={isPasswordCommand ? 'text' : 'password'}
|
||||
value={
|
||||
(isPasswordCommand ? configPassCommand : configPass) ||
|
||||
''
|
||||
}
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
spellCheck="false"
|
||||
onValueChange={(value) => {
|
||||
setConfigPass(value)
|
||||
if (isPasswordCommand) {
|
||||
setConfigPassCommand(value)
|
||||
} else {
|
||||
setConfigPass(value)
|
||||
}
|
||||
}}
|
||||
isClearable={true}
|
||||
onClear={() => {
|
||||
setConfigPass(null)
|
||||
if (isPasswordCommand) {
|
||||
setConfigPassCommand(null)
|
||||
} else {
|
||||
setConfigPass(null)
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
|
||||
@@ -38,12 +38,14 @@ export default function ConfigSyncDrawer({
|
||||
isEncrypted,
|
||||
passCommand,
|
||||
sync,
|
||||
isPasswordCommand,
|
||||
}: {
|
||||
label?: string
|
||||
sync?: string
|
||||
isEncrypted?: boolean
|
||||
pass?: string
|
||||
passCommand?: string
|
||||
isPasswordCommand?: boolean
|
||||
}) => {
|
||||
try {
|
||||
if (!label) {
|
||||
@@ -54,10 +56,8 @@ export default function ConfigSyncDrawer({
|
||||
throw new Error('Path is required')
|
||||
}
|
||||
|
||||
if (isEncrypted) {
|
||||
if (!pass && !passCommand) {
|
||||
throw new Error('Password is required for encrypted configs')
|
||||
}
|
||||
if (isEncrypted && isPasswordCommand && !passCommand) {
|
||||
throw new Error('Password command is required for encrypted configs')
|
||||
}
|
||||
|
||||
setIsSaving(true)
|
||||
@@ -68,8 +68,8 @@ export default function ConfigSyncDrawer({
|
||||
id: generatedId,
|
||||
label,
|
||||
isEncrypted: isEncrypted || false,
|
||||
pass,
|
||||
passCommand,
|
||||
pass: isPasswordCommand ? undefined : pass,
|
||||
passCommand: isPasswordCommand ? passCommand : undefined,
|
||||
sync,
|
||||
})
|
||||
|
||||
@@ -111,12 +111,11 @@ export default function ConfigSyncDrawer({
|
||||
e.preventDefault()
|
||||
handleCreate({
|
||||
label: config.label,
|
||||
pass: isPasswordCommand ? undefined : config.pass,
|
||||
pass: config.pass,
|
||||
isEncrypted: config.isEncrypted,
|
||||
passCommand: isPasswordCommand
|
||||
? config.passCommand
|
||||
: undefined,
|
||||
passCommand: config.passCommand,
|
||||
sync: config.sync,
|
||||
isPasswordCommand: isPasswordCommand,
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -172,7 +171,7 @@ export default function ConfigSyncDrawer({
|
||||
placeholder={
|
||||
isPasswordCommand
|
||||
? 'Enter the password command for your config file'
|
||||
: 'Enter the password for your config file'
|
||||
: 'Leave blank to be prompted on every startup'
|
||||
}
|
||||
type={isPasswordCommand ? 'text' : 'password'}
|
||||
value={isPasswordCommand ? config.passCommand : config.pass}
|
||||
|
||||
Reference in New Issue
Block a user