168 lines
5.8 KiB
TypeScript
168 lines
5.8 KiB
TypeScript
import { AdminLayout } from "./AdminLayout.tsx";
|
|
|
|
export const AAGUIDPage = ({ allowlist }: { allowlist: any[] }) => {
|
|
return (
|
|
<AdminLayout title="AAGUID Allow-List" currentPath="/admin/aaguid">
|
|
<div
|
|
id="status-banner"
|
|
style="display: none; margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: 4px; font-size: 0.9rem;"
|
|
/>
|
|
|
|
<div class="card">
|
|
<h2>AAGUID Allow-List Management</h2>
|
|
<p>
|
|
Manage the enterprise allow-list of approved hardware Authenticator
|
|
Attestation GUIDs (AAGUIDs). If this list is populated, only passkeys
|
|
matching these AAGUIDs will be permitted to register. If empty, all
|
|
certified hardware passkeys are allowed (software passkeys are always
|
|
rejected).
|
|
</p>
|
|
|
|
<form
|
|
id="add-aaguid-form"
|
|
style="margin-bottom: 2rem; display: flex; gap: 1rem; align-items: flex-end;"
|
|
>
|
|
<div style="flex: 1;">
|
|
<label style="display: block; margin-bottom: 0.5rem; font-weight: 500;">
|
|
AAGUID (UUID format)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="aaguid"
|
|
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
required
|
|
style="width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;"
|
|
/>
|
|
</div>
|
|
<div style="flex: 2;">
|
|
<label style="display: block; margin-bottom: 0.5rem; font-weight: 500;">
|
|
Description (e.g. YubiKey 5 NFC)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="description"
|
|
placeholder="Hardware Key Model"
|
|
style="width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<button
|
|
type="submit"
|
|
class="btn-action btn-success"
|
|
style="padding: 0.6rem 1rem;"
|
|
>
|
|
Add to Allow-List
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>AAGUID</th>
|
|
<th>Description</th>
|
|
<th>Added On</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{allowlist.length === 0
|
|
? (
|
|
<tr>
|
|
<td colspan={4} style="text-align: center; color: #6c757d;">
|
|
The allow-list is empty. All hardware passkeys are
|
|
currently accepted.
|
|
</td>
|
|
</tr>
|
|
)
|
|
: (
|
|
allowlist.map((item: any) => (
|
|
<tr key={item.id}>
|
|
<td>
|
|
<code style="background: #f8f9fa; padding: 0.2rem 0.4rem; border-radius: 3px;">
|
|
{item.aaguid}
|
|
</code>
|
|
</td>
|
|
<td>{item.description || "-"}</td>
|
|
<td>{new Date(item.created_at).toLocaleString()}</td>
|
|
<td>
|
|
<button
|
|
type="button"
|
|
class="btn-action btn-warning"
|
|
onclick={`removeAaguid('${item.id}')`}
|
|
>
|
|
Remove
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script
|
|
dangerouslySetInnerHTML={{
|
|
__html: `
|
|
function showNotice(msg, isError) {
|
|
const banner = document.getElementById('status-banner');
|
|
banner.textContent = msg;
|
|
banner.style.display = 'block';
|
|
banner.style.background = isError ? '#f8d7da' : '#d1e7dd';
|
|
banner.style.color = isError ? '#842029' : '#0f5132';
|
|
banner.style.border = isError ? '1px solid #f5c2c7' : '1px solid #badbcc';
|
|
setTimeout(() => { banner.style.display = 'none'; }, 6000);
|
|
}
|
|
|
|
document.getElementById('add-aaguid-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const aaguid = e.target.aaguid.value;
|
|
const description = e.target.description.value;
|
|
|
|
try {
|
|
const res = await fetch('/api/admin/aaguid', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ aaguid, description })
|
|
});
|
|
if (res.ok) {
|
|
showNotice('AAGUID added to allow-list', false);
|
|
setTimeout(() => window.location.reload(), 800);
|
|
} else {
|
|
const data = await res.json();
|
|
showNotice(data.error || 'Failed to add AAGUID', true);
|
|
}
|
|
} catch (err) {
|
|
showNotice('Network error', true);
|
|
}
|
|
});
|
|
|
|
async function removeAaguid(id) {
|
|
if (!confirm('Are you sure you want to remove this AAGUID from the allow-list?')) {
|
|
return;
|
|
}
|
|
try {
|
|
const res = await fetch('/api/admin/aaguid/' + id, {
|
|
method: 'DELETE'
|
|
});
|
|
if (res.ok) {
|
|
showNotice('AAGUID removed', false);
|
|
setTimeout(() => window.location.reload(), 800);
|
|
} else {
|
|
const data = await res.json();
|
|
showNotice(data.error || 'Failed to remove AAGUID', true);
|
|
}
|
|
} catch (err) {
|
|
showNotice('Network error', true);
|
|
}
|
|
}
|
|
`,
|
|
}}
|
|
>
|
|
</script>
|
|
</AdminLayout>
|
|
);
|
|
};
|