Fix refresh logout+ add trash (undo deleted)

This commit is contained in:
2026-05-08 14:39:24 +01:00
parent a4528eb1f7
commit 8743f023b2
6 changed files with 377 additions and 160 deletions
+42 -19
View File
@@ -19,7 +19,6 @@ $db_path = __DIR__ . '/vault.db';
$db = new SQLite3($db_path);
$db->enableExceptions(true);
// Create tables
$db->exec("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -46,14 +45,17 @@ $db->exec("
iv TEXT NOT NULL,
encryption_method TEXT DEFAULT 'server',
folder TEXT DEFAULT 'All',
deleted INTEGER DEFAULT 0,
deleted_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
");
// Fix missing columns (existing databases)
try { $db->exec("ALTER TABLE vault_entries ADD COLUMN encryption_method TEXT DEFAULT 'server'"); } catch (Exception $e) {}
try { $db->exec("ALTER TABLE vault_entries ADD COLUMN folder TEXT DEFAULT 'All'"); } catch (Exception $e) {}
try { $db->exec("ALTER TABLE vault_entries ADD COLUMN deleted INTEGER DEFAULT 0"); } catch (Exception $e) {}
try { $db->exec("ALTER TABLE vault_entries ADD COLUMN deleted_at DATETIME"); } catch (Exception $e) {}
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = str_replace('/password-manager/api.php', '', $path);
@@ -82,7 +84,6 @@ function authenticate($db) {
try {
switch (true) {
// Auth
case ($path === '/register' && $method === 'POST'):
$u = trim($input['username'] ?? '');
$p = $input['masterPassword'] ?? '';
@@ -100,7 +101,6 @@ try {
$st->bindValue(':k', base64_encode($key), SQLITE3_TEXT);
$st->execute();
$uid = $db->lastInsertRowID();
// Insert default folders
$defaultFolders = ['All', 'Social', 'Banking', 'Work', 'Personal'];
$stFolder = $db->prepare('INSERT OR IGNORE INTO folders (user_id, name) VALUES (:uid, :name)');
foreach ($defaultFolders as $name) {
@@ -124,7 +124,6 @@ try {
}
$key = genKey($p, $user['salt']);
$token = base64_encode($user['id'] . ':' . bin2hex(random_bytes(16)) . ':' . base64_encode($key));
// Ensure default folders exist for existing users (idempotent)
$defaultFolders = ['All', 'Social', 'Banking', 'Work', 'Personal'];
$stFolder = $db->prepare('INSERT OR IGNORE INTO folders (user_id, name) VALUES (:uid, :name)');
foreach ($defaultFolders as $name) {
@@ -135,16 +134,13 @@ try {
echo json_encode(['message'=>'OK','token'=>$token,'userId'=>$user['id'],'salt'=>$user['salt']]);
break;
// Folders
case ($path === '/folders' && $method === 'GET'):
$auth = authenticate($db);
$st = $db->prepare('SELECT name FROM folders WHERE user_id=:uid ORDER BY name');
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$res = $st->execute();
$folders = [];
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
$folders[] = $row['name'];
}
while ($row = $res->fetchArray(SQLITE3_ASSOC)) { $folders[] = $row['name']; }
echo json_encode($folders);
break;
@@ -152,13 +148,12 @@ try {
$auth = authenticate($db);
$name = trim($input['name'] ?? '');
if (!$name) { http_response_code(400); echo json_encode(['error'=>'Folder name required']); break; }
if (strtolower($name) === 'all') { http_response_code(400); echo json_encode(['error'=>'Cannot use "All"']); break; }
if (strtolower($name) === 'all') { http_response_code(400); echo json_encode(['error'=>'Cannot use All']); break; }
$st = $db->prepare('INSERT INTO folders (user_id, name) VALUES (:uid, :name)');
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$st->bindValue(':name', $name, SQLITE3_TEXT);
try { $st->execute(); }
catch (Exception $e) { http_response_code(409); echo json_encode(['error'=>'Folder already exists']); break; }
echo json_encode(['message'=>'Folder created', 'name'=>$name]);
try { $st->execute(); } catch (Exception $e) { http_response_code(409); echo json_encode(['error'=>'Folder exists']); break; }
echo json_encode(['message'=>'Created','name'=>$name]);
break;
case (preg_match('/^\/folders\/(.+)$/', $path, $m) && $method === 'DELETE'):
@@ -169,8 +164,7 @@ try {
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$st->bindValue(':name', $folderName, SQLITE3_TEXT);
$st->execute();
if ($db->changes() === 0) { http_response_code(404); echo json_encode(['error'=>'Folder not found']); break; }
// Update entries that had this folder to 'All'
if ($db->changes() === 0) { http_response_code(404); echo json_encode(['error'=>'Not found']); break; }
$stUp = $db->prepare("UPDATE vault_entries SET folder='All' WHERE user_id=:uid AND folder=:f");
$stUp->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$stUp->bindValue(':f', $folderName, SQLITE3_TEXT);
@@ -178,17 +172,19 @@ try {
echo json_encode(['message'=>'Deleted']);
break;
// Entries
// Entries - exclude deleted by default
case ($path === '/entries' && $method === 'GET'):
$auth = authenticate($db);
$q = $_GET['search'] ?? '';
$showDeleted = $_GET['deleted'] ?? '0';
if ($q) {
$st = $db->prepare('SELECT * FROM vault_entries WHERE user_id=:uid AND (site LIKE :q OR username LIKE :q) ORDER BY updated_at DESC');
$st = $db->prepare('SELECT * FROM vault_entries WHERE user_id=:uid AND deleted=:del AND (site LIKE :q OR username LIKE :q) ORDER BY updated_at DESC');
$st->bindValue(':q', "%$q%", SQLITE3_TEXT);
} else {
$st = $db->prepare('SELECT * FROM vault_entries WHERE user_id=:uid ORDER BY updated_at DESC');
$st = $db->prepare('SELECT * FROM vault_entries WHERE user_id=:uid AND deleted=:del ORDER BY updated_at DESC');
}
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$st->bindValue(':del', $showDeleted === '1' ? 1 : 0, SQLITE3_INTEGER);
$res = $st->execute();
$entries = [];
while ($r = $res->fetchArray(SQLITE3_ASSOC)) {
@@ -200,6 +196,8 @@ try {
'iv' => $r['iv'],
'encryption_method' => $r['encryption_method'] ?? 'server',
'folder' => $r['folder'] ?? 'All',
'deleted' => $r['deleted'],
'deleted_at' => $r['deleted_at'],
'created_at' => $r['created_at'],
'updated_at' => $r['updated_at']
];
@@ -251,15 +249,40 @@ try {
echo json_encode(['message'=>'Updated']);
break;
// Soft delete
case (preg_match('/^\/entries\/(\d+)$/', $path, $m) && $method === 'DELETE'):
$auth = authenticate($db);
$st = $db->prepare('DELETE FROM vault_entries WHERE id=:id AND user_id=:uid');
$permanent = $_GET['permanent'] ?? '0';
if ($permanent === '1') {
$st = $db->prepare('DELETE FROM vault_entries WHERE id=:id AND user_id=:uid');
} else {
$st = $db->prepare("UPDATE vault_entries SET deleted=1, deleted_at=datetime('now') WHERE id=:id AND user_id=:uid");
}
$st->bindValue(':id', $m[1], SQLITE3_INTEGER);
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$st->execute();
echo json_encode(['message'=>'Deleted']);
break;
// Restore
case (preg_match('/^\/entries\/(\d+)\/restore$/', $path, $m) && $method === 'POST'):
$auth = authenticate($db);
$st = $db->prepare('UPDATE vault_entries SET deleted=0, deleted_at=NULL, updated_at=datetime(\'now\') WHERE id=:id AND user_id=:uid');
$st->bindValue(':id', $m[1], SQLITE3_INTEGER);
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$st->execute();
echo json_encode(['message'=>'Restored']);
break;
// Empty trash (permanently delete all soft-deleted)
case ($path === '/entries/trash/empty' && $method === 'DELETE'):
$auth = authenticate($db);
$st = $db->prepare('DELETE FROM vault_entries WHERE user_id=:uid AND deleted=1');
$st->bindValue(':uid', $auth['userId'], SQLITE3_INTEGER);
$st->execute();
echo json_encode(['message'=>'Trash emptied']);
break;
default:
http_response_code(404);
echo json_encode(['error'=>'Not found']);