initial commit

This commit is contained in:
root
2026-03-26 15:03:38 +07:00
commit 6464c750f2
19 changed files with 2129 additions and 0 deletions

165
views/passwords.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_password'])) {
$stmt = $pdo->prepare("INSERT INTO passwords (title, username, password, url, notes, owner_id) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$_POST['title'], $_POST['username'], $_POST['password'], $_POST['url'], $_POST['notes'], $user_id]);
}
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bulk_action'])) {
if(isset($_POST['selected_passwords'])) {
$selected = $_POST['selected_passwords'];
$action = $_POST['bulk_action'];
if($action === 'delete') {
foreach($selected as $p_id) {
// Check if owner
$p = query("SELECT owner_id FROM passwords WHERE id=?", [$p_id])->fetch();
if($p && $p['owner_id']==$user_id) {
query("DELETE FROM passwords WHERE id=?", [$p_id]);
}
}
} elseif($action === 'share') {
$share_target = $_POST['share_target']; // 'user_1'
$parts = explode('_', $share_target);
foreach($selected as $p_id) {
$p = query("SELECT owner_id, title FROM passwords WHERE id=?", [$p_id])->fetch();
if($p && $p['owner_id']==$user_id) {
$stmt = $pdo->prepare("INSERT INTO password_shares (password_id, share_type, share_id) VALUES (?, ?, ?)");
$stmt->execute([$p_id, $parts[0], $parts[1]]);
$msg = "A password '{$p['title']}' has been shared with you.";
if($parts[0] === 'user') notify_user($parts[1], $msg);
else notify_group($parts[1], $msg);
}
}
}
}
}
// Fetch passwords (owned + shared)
$user_groups = get_user_groups($user_id);
$my_group_ids = array_column($user_groups, 'id');
$group_in = implode(',', array_fill(0, count($my_group_ids), '?')) ?: '0';
$params = array_merge([$user_id, $user_id], $my_group_ids);
$sql = "SELECT p.*, (p.owner_id = ?) as is_mine FROM passwords p
LEFT JOIN password_shares ps ON p.id = ps.password_id
WHERE p.owner_id = ?
OR (ps.share_type='user' AND ps.share_id = ?)
OR (ps.share_type='group' AND ps.share_id IN ($group_in))
GROUP BY p.id ORDER BY p.title";
$params = array_merge([$user_id, $user_id, $user_id], $my_group_ids);
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$passwords = $stmt->fetchAll();
$all_users = query("SELECT id, username FROM users WHERE id != ?", [$user_id])->fetchAll();
$all_groups = query("SELECT * FROM groups")->fetchAll();
?>
<div class="header">
<h1>Passwords Vault</h1>
</div>
<div class="modal-overlay" id="password-modal">
<div class="modal-content">
<h3 style="margin-bottom:1rem;">Password View</h3>
<input type="text" id="modal-password" class="form-control" readonly style="margin-bottom:1rem; font-family:monospace; font-size:1.1rem;">
<div style="display:flex; gap:0.5rem; justify-content:flex-end;">
<button type="button" class="btn btn-secondary" onclick="document.getElementById('password-modal').classList.remove('active')">Close</button>
</div>
</div>
</div>
<div style="display:grid; grid-template-columns: 1fr 2fr; gap:1.5rem;">
<div class="card">
<h3>Add Password</h3>
<form method="POST" style="margin-top:1rem;">
<div class="form-group"><input type="text" name="title" class="form-control" placeholder="Title/Service" required></div>
<div class="form-group"><input type="text" name="username" class="form-control" placeholder="Username"></div>
<div class="form-group" style="display:flex; gap:0.5rem;">
<input type="password" name="password" id="new_pw" class="form-control" placeholder="Password" required>
<button type="button" class="btn btn-secondary" onclick="genPassword()">🎲</button>
</div>
<div class="form-group"><input type="text" name="url" class="form-control" placeholder="Website URL"></div>
<div class="form-group"><textarea name="notes" class="form-control" placeholder="Notes"></textarea></div>
<button type="submit" name="create_password" class="btn">Save Password</button>
</form>
</div>
<div class="card">
<h3>My Vault</h3>
<form method="POST" id="bulk-form">
<div style="display:flex; justify-content:space-between; margin-bottom:1rem;">
<div style="display:flex; gap:0.5rem;">
<button type="submit" name="bulk_action" value="delete" class="btn btn-sm btn-danger" onclick="return confirm('Delete selected?')">Delete Selected</button>
<div style="display:flex; align-items:center;">
<select name="share_target" class="form-control" style="width:150px; padding:0.25rem;">
<?php foreach($all_users as $u): ?><option value="user_<?= $u['id'] ?>">User: <?= htmlspecialchars($u['username']) ?></option><?php endforeach; ?>
<?php foreach($all_groups as $g): ?><option value="group_<?= $g['id'] ?>">Group: <?= htmlspecialchars($g['name']) ?></option><?php endforeach; ?>
</select>
<button type="submit" name="bulk_action" value="share" class="btn btn-sm" style="margin-left:0.5rem;">Share Selected</button>
</div>
</div>
<div style="color:var(--text-light);font-size:0.875rem;">Shortcuts:<br>Ctrl+B (Copy User), Ctrl+C (Copy Password)<br>Hover row to activate</div>
</div>
<table>
<thead><tr><th><input type="checkbox" onclick="toggleAll(this)"></th><th>Title</th><th>Username</th><th>Actions</th></tr></thead>
<tbody>
<?php foreach($passwords as $p): ?>
<tr class="pw-row" data-pw="<?= htmlspecialchars($p['password']) ?>" data-usr="<?= htmlspecialchars((string)$p['username']) ?>">
<td><?php if($p['is_mine']): ?><input type="checkbox" name="selected_passwords[]" value="<?= $p['id'] ?>" class="pw-checkbox"><?php endif; ?></td>
<td><?= htmlspecialchars($p['title']) ?> <?= !$p['is_mine'] ? '<span class="badge" style="background:var(--secondary);">Shared</span>' : '' ?></td>
<td><?= htmlspecialchars((string)$p['username']) ?></td>
<td>
<button type="button" onclick="showGw('<?= htmlspecialchars(addslashes($p['password'])) ?>')" class="btn btn-sm btn-secondary">👀 View</button>
<button type="button" onclick="copyText('<?= htmlspecialchars(addslashes($p['password'])) ?>')" class="btn btn-sm">📋 Copy Pw</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</form>
</div>
</div>
<script>
function genPassword() {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+";
let p = "";
for(let i=0; i<16; i++) p += chars[Math.floor(Math.random()*chars.length)];
document.getElementById('new_pw').value = p;
document.getElementById('new_pw').type = 'text'; // show it temporarily
}
function showGw(pw) {
document.getElementById('modal-password').value = pw;
document.getElementById('password-modal').classList.add('active');
}
function toggleAll(chk) {
document.querySelectorAll('.pw-checkbox').forEach(c => c.checked = chk.checked);
}
function copyText(txt) {
navigator.clipboard.writeText(txt);
showToast('Copied to clipboard!');
}
// Hotkeys Ctrl+C and Ctrl+B on hovered row
let hoveredRow = null;
document.querySelectorAll('.pw-row').forEach(row => {
row.addEventListener('mouseenter', () => hoveredRow = row);
row.addEventListener('mouseleave', () => hoveredRow = null);
});
document.addEventListener('keydown', (e) => {
if(hoveredRow && e.ctrlKey) {
if(e.key.toLowerCase() === 'c') {
copyText(hoveredRow.getAttribute('data-pw'));
e.preventDefault();
} else if(e.key.toLowerCase() === 'b') {
copyText(hoveredRow.getAttribute('data-usr'));
e.preventDefault();
}
}
});
</script>