diff --git a/app/about/page.tsx b/app/about/page.tsx new file mode 100644 index 0000000..deec6a2 --- /dev/null +++ b/app/about/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { About } from "@/components/About"; + +export default function AboutPage() { + return ; +} diff --git a/components/About.tsx b/components/About.tsx new file mode 100644 index 0000000..63f8437 --- /dev/null +++ b/components/About.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Github, Heart } from "lucide-react"; +import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; +import { CustomTitleBar } from "@/components/CustomTitleBar"; +import { open } from "@tauri-apps/plugin-shell"; + +export function About() { + const handleClose = async () => { + const window = getCurrentWebviewWindow(); + await window.close(); + }; + + return ( +
+ + + +
+ {/* App Icon and Title */} +
+ App Icon +
+

Simple Password Manager

+

Version 1.0.0

+
+
+ + {/* Description */} +

+ A secure and modern password manager built with the proven KeePass database format. + Keep your passwords safe with strong encryption. +

+ + {/* Links */} +
+ +
+ + {/* Tech Stack - Simple badges */} +
+ Tauri + React + Next.js + Rust + TypeScript +
+ + {/* Footer */} +
+

+ Made with by Jonas Laux +

+

+ Open Source • MIT License +

+
+
+
+
+ ); +} diff --git a/components/AboutDialog.tsx b/components/AboutDialog.tsx new file mode 100644 index 0000000..02fbebb --- /dev/null +++ b/components/AboutDialog.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { ExternalLink, Github } from "lucide-react"; + +interface AboutDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function AboutDialog({ open, onOpenChange }: AboutDialogProps) { + return ( + + + +
+ App Icon +
+ Simple Password Manager + + Version 1.0.0 + +
+
+
+ +
+

+ A secure, modern password manager built with KeePass database format (.kdbx). + Keep your passwords safe with strong encryption and a clean, intuitive interface. +

+ +
+

Features

+
    +
  • KeePass 2.x database format support
  • +
  • Strong encryption (AES-256, ChaCha20)
  • +
  • Password generator with strength meter
  • +
  • Breach checking with Have I Been Pwned
  • +
  • Auto-lock and quick unlock
  • +
  • Cross-platform support
  • +
+
+ +
+ + +
+ +
+

+ Built with Tauri, React, and Next.js +

+

+ © 2026 Simple Password Manager. Open Source Software. +

+
+
+
+
+ ); +} diff --git a/components/CreateDatabaseDialog.tsx b/components/CreateDatabaseDialog.tsx index 924b783..6445a0b 100644 --- a/components/CreateDatabaseDialog.tsx +++ b/components/CreateDatabaseDialog.tsx @@ -13,22 +13,24 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { FolderOpen } from "lucide-react"; -import { createDatabase } from "@/lib/tauri"; +import { createDatabase, openDatabaseInNewInstance } from "@/lib/tauri"; import { useToast } from "@/components/ui/use-toast"; import { open } from "@tauri-apps/plugin-dialog"; -import { saveLastDatabasePath } from "@/lib/storage"; +import { saveLastDatabasePath, addRecentDatabase } from "@/lib/storage"; import { PasswordStrengthMeter } from "@/components/PasswordStrengthMeter"; interface CreateDatabaseDialogProps { isOpen: boolean; onClose: () => void; - onSuccess: () => void; + onSuccess: (openedInNewInstance: boolean) => void; + hasOpenDatabase?: boolean; } export function CreateDatabaseDialog({ isOpen, onClose, onSuccess, + hasOpenDatabase = false, }: CreateDatabaseDialogProps) { const [folderPath, setFolderPath] = useState(""); const [fileName, setFileName] = useState(""); @@ -92,20 +94,32 @@ export function CreateDatabaseDialog({ const fullPath = `${folderPath}\\${fileNameWithExt}`; await createDatabase(fullPath, password); - saveLastDatabasePath(fullPath); + addRecentDatabase(fullPath); - toast({ - title: "Success", - description: "Database created successfully", - variant: "success", - }); + if (hasOpenDatabase) { + // Open in new instance if a database is already open + await openDatabaseInNewInstance(fullPath); + toast({ + title: "Success", + description: "Database created and opened in new window", + variant: "success", + }); + onSuccess(true); + } else { + // Open in current instance + saveLastDatabasePath(fullPath); + toast({ + title: "Success", + description: "Database created successfully", + variant: "success", + }); + onSuccess(false); + } setFolderPath(""); setFileName(""); setPassword(""); setConfirmPassword(""); - - onSuccess(); onClose(); } catch (error: any) { toast({ diff --git a/components/CustomTitleBar.tsx b/components/CustomTitleBar.tsx index 0b2f11d..0895b5a 100644 --- a/components/CustomTitleBar.tsx +++ b/components/CustomTitleBar.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { getCurrentWindow } from "@tauri-apps/api/window"; -import { Minus, Square, X, Copy, Save, LogOut } from "lucide-react"; +import { Minus, Square, X, Save, LogOut, Undo2, Copy, Redo2, FolderOpen, Database as DatabaseIcon, Info } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Settings } from "@/components/animate-ui/icons/settings"; import { Search } from "@/components/animate-ui/icons/search"; @@ -12,8 +12,13 @@ import { DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, } from "@/components/ui/dropdown-menu"; import { openSettingsWindow } from "@/lib/window"; +import { getValidatedRecentDatabases } from "@/lib/storage"; +import { openDatabaseInNewInstance } from "@/lib/tauri"; interface CustomTitleBarProps { title?: string; @@ -23,6 +28,15 @@ interface CustomTitleBarProps { onSave?: () => void; onLogout?: () => void; onToggleSearch?: () => void; + onUndo?: () => void; + onRedo?: () => void; + onCopy?: () => void; + onNewDatabase?: () => void; + onTogglePasswords?: () => void; + onAbout?: () => void; + canUndo?: boolean; + canRedo?: boolean; + passwordsVisible?: boolean; } export function CustomTitleBar({ @@ -33,10 +47,38 @@ export function CustomTitleBar({ onSave, onLogout, onToggleSearch, + onUndo, + onRedo, + onCopy, + onNewDatabase, + onTogglePasswords, + onAbout, + canUndo = false, + canRedo = false, + passwordsVisible = false, }: CustomTitleBarProps) { const [isMaximized, setIsMaximized] = useState(false); const [isSettingsHovered, setIsSettingsHovered] = useState(false); const [isSearchHovered, setIsSearchHovered] = useState(false); + const [recentDatabases, setRecentDatabases] = useState([]); + + useEffect(() => { + // Load and validate recent databases on mount + const loadRecentDatabases = async () => { + const validated = await getValidatedRecentDatabases(); + setRecentDatabases(validated); + }; + loadRecentDatabases(); + }, []); + + const handleOpenDatabase = async (dbPath: string) => { + try { + await openDatabaseInNewInstance(dbPath); + } catch (error) { + console.error('Failed to open database in new instance:', error); + alert(`Could not open database: ${error}`); + } + }; useEffect(() => { const checkMaximized = async () => { @@ -92,52 +134,158 @@ export function CustomTitleBar({ } }} > - {/* Left: Icon + Optional File Menu (only for main app) */} -
+ {/* Left: Icon + Optional Menus (only for main app) */} +
App Icon {showMenu && ( - - - - - - - - Save Database - Ctrl+S - - - - - Logout - - - + <> + {/* File Menu */} + + + + + + + + New Database + Ctrl+N + + + + + Open Recent + + + {recentDatabases.length > 0 ? ( + recentDatabases.map((dbPath) => ( + handleOpenDatabase(dbPath)} + className="gap-2 cursor-pointer" + > + + + {dbPath.split(/[/\\]/).pop() || dbPath} + + + )) + ) : ( + + No recent databases + + )} + + + + + + Save Database + Ctrl+S + + + + + Close Database + Ctrl+W + + + + + {/* Edit Menu */} + + + + + + + + Undo + Ctrl+Z + + + + Redo + Ctrl+Y + + + + + {/* View Menu */} + + + + + + + + Toggle Search + Ctrl+F + + + + + {/* Help Menu */} + + + + + + + + About + + + + )}
{/* Center: Title */}
- + {title}
{/* Right: Optional Search + Settings + Window Controls */} -
+
{showMenu && onToggleSearch && (
@@ -738,6 +879,13 @@ export function MainApp({ onClose }: MainAppProps) { onOverwrite={handleOverwrite} onCancel={handleConflictCancel} /> + + setShowCreateDatabaseDialog(false)} + onSuccess={handleNewDatabaseSuccess} + hasOpenDatabase={rootGroup !== null} + />
diff --git a/lib/storage.ts b/lib/storage.ts index 4e7feff..8451f09 100644 --- a/lib/storage.ts +++ b/lib/storage.ts @@ -1,6 +1,8 @@ import { invoke } from "@tauri-apps/api/core"; +import { validateDatabaseFile } from "./tauri"; const LAST_DATABASE_KEY = "lastDatabasePath"; +const RECENT_DATABASES_KEY = "recentDatabases"; const COLUMN_CONFIG_PREFIX = "columnConfig_"; const COLUMN_WIDTHS_PREFIX = "columnWidths_"; const HIBP_ENABLED_KEY = "hibpEnabled"; @@ -26,6 +28,82 @@ export function clearLastDatabasePath(): void { } } +export function addRecentDatabase(path: string): void { + if (typeof window !== "undefined") { + const recent = getRecentDatabases(); + const filtered = recent.filter(p => p !== path); + const updated = [path, ...filtered].slice(0, 10); + localStorage.setItem(RECENT_DATABASES_KEY, JSON.stringify(updated)); + } +} + +export function getRecentDatabases(): string[] { + if (typeof window !== "undefined") { + const stored = localStorage.getItem(RECENT_DATABASES_KEY); + if (stored) { + try { + return JSON.parse(stored); + } catch { + return []; + } + } + } + return []; +} + +/** + * Validates all recent database paths and returns only valid ones. + * This function checks if each path exists and is a valid KDBX file. + * Invalid paths are automatically removed from localStorage. + * + * Note: Validates paths concurrently for better performance. Since the recent + * databases list is limited to 10 items (see addRecentDatabase), this should + * not cause file system issues. + */ +export async function getValidatedRecentDatabases(): Promise { + const recent = getRecentDatabases(); + if (recent.length === 0) { + return []; + } + + // Validate each path concurrently (limited to 10 max by addRecentDatabase) + // Note: File system operations are fast (checking existence + reading 8 bytes), + // and Tauri invoke calls have built-in timeouts, so explicit timeout handling + // is not necessary here. + const validationResults = await Promise.all( + recent.map(async (path) => { + try { + const isValid = await validateDatabaseFile(path); + return { path, isValid }; + } catch (error) { + // If validation fails (e.g., file system error), consider it invalid + console.warn(`Failed to validate database path: ${path}`, error); + return { path, isValid: false }; + } + }) + ); + + // Filter to only valid paths + const validPaths = validationResults + .filter(result => result.isValid) + .map(result => result.path); + + // If some paths were invalid, update localStorage to remove them + if (validPaths.length !== recent.length && typeof window !== "undefined") { + localStorage.setItem(RECENT_DATABASES_KEY, JSON.stringify(validPaths)); + } + + return validPaths; +} + +export function clearRecentDatabase(path: string): void { + if (typeof window !== "undefined") { + const recent = getRecentDatabases(); + const filtered = recent.filter(p => p !== path); + localStorage.setItem(RECENT_DATABASES_KEY, JSON.stringify(filtered)); + } +} + // Column configuration per database export interface ColumnVisibility { title: boolean; diff --git a/lib/tauri.ts b/lib/tauri.ts index 6e6d2fd..9c17a38 100644 --- a/lib/tauri.ts +++ b/lib/tauri.ts @@ -184,3 +184,11 @@ export async function getDashboardStats(): Promise { export async function checkBreachedPasswords(): Promise { return await invoke("check_breached_passwords"); } + +export async function openDatabaseInNewInstance(dbPath: string): Promise { + return await invoke("open_database_in_new_instance", { dbPath }); +} + +export async function validateDatabaseFile(path: string): Promise { + return await invoke("validate_database_file", { path }); +} diff --git a/lib/window.ts b/lib/window.ts index 2bfc7fc..45dc7e0 100644 --- a/lib/window.ts +++ b/lib/window.ts @@ -2,7 +2,7 @@ import { WebviewWindow, getAllWebviewWindows } from "@tauri-apps/api/webviewWind import type { EntryData } from "@/lib/tauri"; // Labels for child windows that should be closed when main app closes/logs out -const CHILD_WINDOW_PREFIXES = ['entry-', 'settings']; +const CHILD_WINDOW_PREFIXES = ['entry-', 'settings', 'about']; /** * Check if there are any open child windows (entry editors, settings) @@ -172,3 +172,43 @@ export async function openSettingsWindow() { throw error; } } + +export async function openAboutWindow() { + try { + const windowLabel = "about"; + + // Check if window already exists + const existingWindow = await WebviewWindow.getByLabel(windowLabel); + if (existingWindow) { + await existingWindow.setFocus(); + return; + } + + const isDev = process.env.NODE_ENV === 'development'; + const baseUrl = isDev ? 'http://localhost:3000' : window.location.origin; + + const webview = new WebviewWindow(windowLabel, { + url: `${baseUrl}/about`, + title: "About", + width: 600, + height: 700, + resizable: false, + maximizable: false, + decorations: false, + center: true, + }); + + console.log('Creating about window'); + + webview.once("tauri://created", () => { + console.log("About window created successfully"); + }); + + webview.once("tauri://error", (e) => { + console.error("Error creating about window:", e); + }); + } catch (error) { + console.error("Failed to open about window:", error); + throw error; + } +} diff --git a/package-lock.json b/package-lock.json index 4f0a69b..52b04a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,7 +101,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -361,7 +360,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2921,7 +2919,6 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2932,7 +2929,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2982,7 +2978,6 @@ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", @@ -3469,7 +3464,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3895,7 +3889,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4554,7 +4547,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4740,7 +4732,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5991,7 +5982,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -6761,7 +6751,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6956,7 +6945,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6966,7 +6954,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7716,7 +7703,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -7845,7 +7831,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8000,7 +7985,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8325,7 +8309,6 @@ "integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src-tauri/src/commands/database.rs b/src-tauri/src/commands/database.rs index 1f0e0f8..e83553d 100644 --- a/src-tauri/src/commands/database.rs +++ b/src-tauri/src/commands/database.rs @@ -1,7 +1,10 @@ use crate::kdbx::{Database, GroupData, KdfInfo}; use crate::state::AppState; +use std::io::Read; use std::path::PathBuf; use tauri::State; +use std::process::Command; +use std::fs::File; #[tauri::command] pub fn get_initial_file_path(state: State) -> Option { @@ -122,6 +125,63 @@ pub fn check_database_changes(state: State) -> Result { } } +#[tauri::command] +pub fn open_database_in_new_instance(db_path: String) -> Result<(), String> { + let current_exe = std::env::current_exe() + .map_err(|e| format!("Failed to get current executable path: {}", e))?; + + Command::new(current_exe) + .arg(&db_path) + .spawn() + .map_err(|e| format!("Failed to spawn new instance: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub fn validate_database_file(path: String) -> Result { + let path_buf = PathBuf::from(&path); + + // Check if file exists + if !path_buf.exists() { + return Ok(false); + } + + // Check if it's a file (not a directory) + if !path_buf.is_file() { + return Ok(false); + } + + // Check .kdbx extension + if let Some(ext) = path_buf.extension() { + if ext.to_string_lossy().to_lowercase() != "kdbx" { + return Ok(false); + } + } else { + return Ok(false); + } + + // Validate KDBX magic bytes (0x03D9A29A) + // KDBX format starts with these 4 bytes after the base signature + let mut file = File::open(&path_buf) + .map_err(|_| "Failed to open file for validation".to_string())?; + + let mut magic_bytes = [0u8; 8]; + if file.read_exact(&mut magic_bytes).is_err() { + // File is too small to be a valid KDBX file + return Ok(false); + } + + // Check for KDBX signature: first 4 bytes should be 0x03, 0xD9, 0xA2, 0x9A + // followed by version bytes + let valid = magic_bytes[0] == 0x03 + && magic_bytes[1] == 0xD9 + && magic_bytes[2] == 0xA2 + && magic_bytes[3] == 0x9A; + + Ok(valid) +} + #[tauri::command] pub fn merge_database(state: State) -> Result<(), String> { let mut database_lock = state.database.lock() diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1c7a859..a7c7f74 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -34,6 +34,8 @@ fn main() { commands::database::upgrade_kdf_parameters, commands::database::check_database_changes, commands::database::merge_database, + commands::database::open_database_in_new_instance, + commands::database::validate_database_file, commands::database::get_groups, commands::entry::get_entries, commands::entry::get_favorite_entries, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7717f9a..170e453 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -57,7 +57,9 @@ "fs:allow-read-dir", "clipboard-manager:allow-write-text", "clipboard-manager:allow-clear", - "shell:allow-open" + "shell:allow-open", + "shell:allow-spawn", + "shell:allow-execute" ] }, { @@ -89,6 +91,20 @@ "core:event:allow-emit", "dialog:allow-ask" ] + }, + { + "identifier": "about-capability", + "windows": ["about"], + "permissions": [ + "core:default", + "core:window:allow-close", + "core:window:allow-destroy", + "core:window:allow-minimize", + "core:window:allow-is-maximized", + "core:window:allow-start-dragging", + "core:event:allow-listen", + "shell:allow-open" + ] } ] }