PHP WebShell
Текущая директория: /var/www/bitcardoApp/backyard/user/security
Просмотр файла: 2fa.php
<?php
// backyard/user/security/2fa.php
include '../common/header.php';
if (!isset($conn)) { include_once '../../config/db_config.php'; }
require_once '../../models/security/2fa.php';
date_default_timezone_set('Africa/Lagos');
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
$filters = [
'q' => $_GET['q'] ?? '',
'status' => $_GET['status'] ?? 'all',
'only_totp' => isset($_GET['only_totp']) ? 1 : 0,
'page' => (int)($_GET['page'] ?? 1),
'per_page' => (int)($_GET['per_page'] ?? 25),
];
$list = fa_list_users($conn, $filters);
function qurl(array $add=[]){ $qs = array_merge($_GET,$add); return '?'.http_build_query($qs); }
?>
<style>
.code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
@media (max-width: 767.98px){
.stack-sm>*{ margin-bottom:.5rem; }
.nk-block-head .nk-block-between-md { gap:.75rem; }
}
</style>
<div class="nk-content nk-content-fluid mt-5">
<div class="container-xl wide-lg">
<div class="nk-content-body">
<div class="nk-block-head">
<div class="nk-block-between-md g-3">
<div class="nk-block-head-content">
<h4 class="nk-block-title fw-normal mb-1">2FA Admin</h4>
<p class="text-soft mb-0">See who has TOTP enabled, enable/disable, reset, and regenerate backup codes.</p>
</div>
<div class="nk-block-head-content">
<a href="../dashboard/index.php" class="btn btn-outline-secondary btn-sm">Back</a>
</div>
</div>
</div>
<div class="nk-block">
<div class="card card-bordered">
<div class="card-inner">
<form class="row g-2 mb-2" method="get">
<div class="col-12 col-md-4">
<label class="form-label small">Search user (name / email / phone)</label>
<input type="text" class="form-control" name="q" value="<?= h($filters['q']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Status</label>
<select class="form-select" name="status">
<option value="all" <?= $filters['status']==='all'?'selected':''; ?>>All</option>
<option value="active" <?= $filters['status']==='active'?'selected':''; ?>>Active</option>
<option value="inactive" <?= $filters['status']==='inactive'?'selected':''; ?>>Inactive</option>
</select>
</div>
<div class="col-6 col-md-2 d-flex align-items-end">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="only_totp" name="only_totp" <?= $filters['only_totp']?'checked':''; ?>>
<label class="form-check-label small" for="only_totp">Only with TOTP</label>
</div>
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Per Page</label>
<select class="form-select" name="per_page" onchange="this.form.submit()">
<?php foreach([25,50,100] as $pp): ?>
<option value="<?= $pp; ?>" <?= $filters['per_page']==$pp?'selected':''; ?>><?= $pp; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6 col-md-2 d-flex align-items-end justify-content-end">
<button class="btn btn-primary w-100 w-md-auto">Apply</button>
</div>
</form>
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Users</h6>
<div class="small text-soft"><?= number_format($list['total']); ?> total</div>
</div>
<!-- Mobile list -->
<div class="d-md-none">
<?php if(empty($list['rows'])): ?>
<div class="alert alert-light border mb-0">No users found.</div>
<?php else: foreach($list['rows'] as $u):
$name = trim(($u['first_name']??'').' '.($u['last_name']??''));
$enabled = (int)($u['totp_enabled'] ?? 0) === 1;
?>
<div class="border rounded-3 p-3 mb-2">
<div class="d-flex justify-content-between">
<div>
<div class="fw-semibold"><?= h($name ?: '—'); ?></div>
<div class="small text-soft"><?= h($u['email'] ?: $u['phone'] ?: ''); ?></div>
</div>
<span class="badge <?= $enabled?'bg-success':'bg-secondary'; ?>"><?= $enabled?'TOTP On':'TOTP Off'; ?></span>
</div>
<div class="d-flex gap-2 mt-2">
<?php if($enabled): ?>
<button class="btn btn-sm btn-outline-warning" onclick="faAction(<?= (int)$u['user_id'];?>,'disable')">Disable</button>
<button class="btn btn-sm btn-outline-danger" onclick="faAction(<?= (int)$u['user_id'];?>,'reset')">Reset TOTP</button>
<?php else: ?>
<button class="btn btn-sm btn-outline-success" onclick="faAction(<?= (int)$u['user_id'];?>,'enable')">Enable</button>
<?php endif; ?>
<button class="btn btn-sm btn-outline-primary" onclick="regenCodes(<?= (int)$u['user_id'];?>)">Regenerate Codes</button>
</div>
</div>
<?php endforeach; endif; ?>
</div>
<!-- Desktop table -->
<div class="table-responsive d-none d-md-block">
<table class="table table-striped align-middle">
<thead class="small text-soft">
<tr>
<th style="width:80px;">User ID</th>
<th>Name</th>
<th>Contact</th>
<th>Status</th>
<th>TOTP</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php if(empty($list['rows'])): ?>
<tr><td colspan="6" class="text-center text-muted py-4">No users found.</td></tr>
<?php else: foreach($list['rows'] as $u):
$name = trim(($u['first_name']??'').' '.($u['last_name']??''));
$enabled = (int)($u['totp_enabled'] ?? 0) === 1;
?>
<tr>
<td class="code"><?= (int)$u['user_id']; ?></td>
<td><?= h($name ?: '—'); ?></td>
<td class="small text-soft"><?= h($u['email'] ?: $u['phone'] ?: '—'); ?></td>
<td>
<?php if (strtolower((string)$u['user_status'])==='active'): ?>
<span class="badge bg-success">Active</span>
<?php else: ?>
<span class="badge bg-danger">Inactive</span>
<?php endif; ?>
</td>
<td><span class="badge <?= $enabled?'bg-success':'bg-secondary'; ?>"><?= $enabled?'Enabled':'Disabled'; ?></span></td>
<td class="text-end">
<?php if($enabled): ?>
<button class="btn btn-xs btn-outline-warning me-1" onclick="faAction(<?= (int)$u['user_id'];?>,'disable')">Disable</button>
<button class="btn btn-xs btn-outline-danger me-1" onclick="faAction(<?= (int)$u['user_id'];?>,'reset')">Reset</button>
<?php else: ?>
<button class="btn btn-xs btn-outline-success me-1" onclick="faAction(<?= (int)$u['user_id'];?>,'enable')">Enable</button>
<?php endif; ?>
<button class="btn btn-xs btn-outline-primary" onclick="regenCodes(<?= (int)$u['user_id'];?>)">Regen Codes</button>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
<?php if($list['pages']>1):
$p=$list['page']; $pages=$list['pages']; $prev=max(1,$p-1); $next=min($pages,$p+1); ?>
<div class="d-flex justify-content-between align-items-center mt-2">
<div class="small text-soft">Page <?= $p; ?> of <?= $pages; ?></div>
<ul class="pagination pagination-sm mb-0">
<li class="page-item <?= $p<=1?'disabled':''; ?>"><a class="page-link" href="<?= qurl(['page'=>1]); ?>">« First</a></li>
<li class="page-item <?= $p<=1?'disabled':''; ?>"><a class="page-link" href="<?= qurl(['page'=>$prev]); ?>">‹ Prev</a></li>
<li class="page-item disabled"><span class="page-link"><?= $p; ?></span></li>
<li class="page-item <?= $p>=$pages?'disabled':''; ?>"><a class="page-link" href="<?= qurl(['page'=>$next]); ?>">Next ›</a></li>
<li class="page-item <?= $p>=$pages?'disabled':''; ?>"><a class="page-link" href="<?= qurl(['page'=>$pages]); ?>">Last »</a></li>
</ul>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Codes Modal -->
<div class="modal fade" id="codesModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Backup Codes (displayed once)</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="codesBox" class="code"></div>
<div class="small text-soft mt-2">Store these codes securely. They will not be shown again.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
async function faAction(user_id, action){
const labels = {enable:'Enable TOTP for this user?', disable:'Disable TOTP for this user?', reset:'Reset (wipe) TOTP for this user?'};
if(!confirm(labels[action] || 'Proceed?')) return;
try{
const resp = await fetch('../../models/security/2fa_actions.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({action, user_id})
});
const txt = await resp.text();
let data; try{ data = JSON.parse(txt); }catch(e){ throw new Error('Bad JSON: '+txt.substring(0,160)); }
if(!data.ok){ alert('Failed: ' + (data.error||'Unknown')); return; }
location.reload();
}catch(err){ alert(err.message||'Network error'); }
}
async function regenCodes(user_id){
if(!confirm('Regenerate backup codes for this user? Old codes will be invalidated.')) return;
try{
const resp = await fetch('../../models/security/2fa_actions.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({action:'regen_codes', user_id})
});
const txt = await resp.text();
let data; try{ data = JSON.parse(txt); }catch(e){ throw new Error('Bad JSON: '+txt.substring(0,160)); }
if(!data.ok){ alert('Failed: ' + (data.error||'Unknown')); return; }
const box = document.getElementById('codesBox');
box.innerHTML = (data.codes||[]).map(c => `<div>${c}</div>`).join('');
bootstrap.Modal.getOrCreateInstance(document.getElementById('codesModal')).show();
}catch(err){ alert(err.message||'Network error'); }
}
</script>
<?php include '../common/footer.php'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!