166 lines
8.2 KiB
PHP
166 lines
8.2 KiB
PHP
|
|
<?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>
|