feat(security): add phase8 hardening with rate limits, audit logs, and auth-first simulator flow
This commit is contained in:
@@ -123,6 +123,25 @@
|
||||
</p>
|
||||
|
||||
<div class="grid">
|
||||
<section class="panel">
|
||||
<h2>Auth</h2>
|
||||
<label>Name</label>
|
||||
<input id="authName" placeholder="optional display name" />
|
||||
<label>Email</label>
|
||||
<input id="authEmail" placeholder="you@example.com" />
|
||||
<label>Password</label>
|
||||
<input id="authPassword" type="password" placeholder="password" />
|
||||
<div class="row">
|
||||
<button id="signUpBtn" class="alt">Sign Up</button>
|
||||
<button id="signInBtn">Sign In</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button id="sessionBtn" class="alt">Check Session</button>
|
||||
<button id="signOutBtn" class="danger">Sign Out</button>
|
||||
</div>
|
||||
<pre id="authState"></pre>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Device Bootstrap</h2>
|
||||
<label>Device Name</label>
|
||||
@@ -186,6 +205,7 @@
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const state = {
|
||||
session: null,
|
||||
device: null,
|
||||
deviceToken: null,
|
||||
socket: null,
|
||||
@@ -209,6 +229,7 @@
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
$('authState').textContent = JSON.stringify({ session: state.session }, null, 2);
|
||||
$('deviceState').textContent = JSON.stringify({ device: state.device, hasToken: Boolean(state.deviceToken) }, null, 2);
|
||||
$('clientState').textContent = JSON.stringify({ lastStreamSessionId: state.lastStreamSessionId }, null, 2);
|
||||
$('cameraState').textContent = JSON.stringify(
|
||||
@@ -222,6 +243,64 @@
|
||||
);
|
||||
};
|
||||
|
||||
const getAuthPayload = () => ({
|
||||
name: $('authName').value.trim() || undefined,
|
||||
email: $('authEmail').value.trim(),
|
||||
password: $('authPassword').value,
|
||||
});
|
||||
|
||||
$('signUpBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const authPayload = getAuthPayload();
|
||||
const payload = await authFetch('/api/auth/sign-up/email', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(authPayload),
|
||||
});
|
||||
log('sign up', payload);
|
||||
$('sessionBtn').click();
|
||||
} catch (error) {
|
||||
log('sign up failed', { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
$('signInBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const authPayload = getAuthPayload();
|
||||
const payload = await authFetch('/api/auth/sign-in/email', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email: authPayload.email, password: authPayload.password }),
|
||||
});
|
||||
log('sign in', payload);
|
||||
$('sessionBtn').click();
|
||||
} catch (error) {
|
||||
log('sign in failed', { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
$('sessionBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const payload = await authFetch('/api/auth/get-session');
|
||||
state.session = payload?.session ? payload : null;
|
||||
render();
|
||||
log('session check', payload);
|
||||
} catch (error) {
|
||||
state.session = null;
|
||||
render();
|
||||
log('session check failed', { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
$('signOutBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const payload = await authFetch('/api/auth/sign-out', { method: 'POST', body: JSON.stringify({}) });
|
||||
state.session = null;
|
||||
render();
|
||||
log('sign out', payload);
|
||||
} catch (error) {
|
||||
log('sign out failed', { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
const authFetch = async (url, options = {}) => {
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include',
|
||||
@@ -337,6 +416,7 @@
|
||||
|
||||
$('registerBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
if (!state.session) throw new Error('Authenticate first');
|
||||
const role = $('role').value;
|
||||
const name = $('deviceName').value.trim();
|
||||
const payload = await authFetch('/devices/register', {
|
||||
@@ -370,6 +450,24 @@
|
||||
log('loaded saved device', parsed);
|
||||
});
|
||||
|
||||
const auditPanel = document.createElement('section');
|
||||
auditPanel.className = 'panel';
|
||||
auditPanel.style.marginTop = '16px';
|
||||
auditPanel.innerHTML = `
|
||||
<h2>Audit Logs</h2>
|
||||
<button id="fetchAuditBtn" class="alt">Fetch My Device Audit Logs</button>
|
||||
`;
|
||||
document.querySelector('.page').appendChild(auditPanel);
|
||||
|
||||
$('fetchAuditBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const payload = await deviceFetch('/audit/device');
|
||||
log('audit logs', payload);
|
||||
} catch (error) {
|
||||
log('audit fetch failed', { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
$('loadSavedBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
if (!state.device?.id) return;
|
||||
|
||||
Reference in New Issue
Block a user