feat: surface failed token sign-in alerts (Closes #543)
CI / lint (pull_request) Successful in 1m10s
CI / build-release (pull_request) Successful in 4s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-11 05:35:35 +00:00
1 parent 98aecd8255
commit 350bfc067d
8 files changed
+300 -2

No files matched your search

+2
View File
@@ -30,6 +30,8 @@ button { background: linear-gradient(180deg,#1f3a5f,#15324d); border:1px solid #
.security-activity-header h3, .security-activity-header p { margin:0 0 6px; }
.security-activity-list { display:grid; gap:8px; margin:12px 0; }
.security-event { padding:12px; border:1px solid #243d5d; border-radius:12px; background:#0d1c30; }
.authentication-alert { border-color:#d69e2e; background:#241b09; }
.authentication-alert strong { color:#f6c453; }
.security-event strong, .security-event span { display:block; overflow-wrap:anywhere; }
#load-more-security-activity { width:100%; min-height:44px; }
button:hover { filter: brightness(1.15); }
+32
View File
@@ -142,6 +142,20 @@
}
try {
const page = await boundary.listSecurityEvents(append ? activityCursor : null);
if (!append) {
page.authentication_alerts.forEach(alert => {
const row = root.document.createElement('article');
row.className = 'security-event authentication-alert';
const formatted = boundary.formatAuthenticationAlert(alert);
const title = root.document.createElement('strong');
title.textContent = formatted.title;
const details = root.document.createElement('span');
details.className = 'small muted';
details.textContent = formatted.detail;
row.append(title, details);
activityList.append(row);
});
}
const labels = {
sign_in: 'Signed in',
sign_out: 'Signed out',
@@ -665,6 +679,21 @@
return response.json();
}
function formatAuthenticationAlert(alert) {
const failed = Math.max(0, Number(alert?.failed_count) || 0);
const blocked = Math.max(0, Number(alert?.blocked_count) || 0);
const method = alert?.method === 'passkey' ? 'passkey' : 'token';
const title = `${failed} failed ${method} sign-in${failed === 1 ? '' : 's'}`
+ (blocked ? ` · ${blocked} blocked` : '');
const formatTime = value => new Date(Number(value) * 1000).toLocaleString(
'en-US', { timeZone: 'UTC' },
);
return {
title,
detail: `${formatTime(alert?.first_at)} – ${formatTime(alert?.last_at)}`,
};
}
async function listSecurityEvents(cursor = null) {
const query = new URLSearchParams({ limit: '25' });
if (Number.isInteger(cursor) && cursor > 0) query.set('cursor', String(cursor));
@@ -673,6 +702,8 @@
const payload = await response.json();
return {
events: Array.isArray(payload.events) ? payload.events : [],
authentication_alerts: Array.isArray(payload.authentication_alerts)
? payload.authentication_alerts : [],
next_cursor: Number.isInteger(payload.next_cursor) ? payload.next_cursor : null,
};
}
@@ -722,6 +753,7 @@
listActiveDevices,
listPasskeys,
listSecurityEvents,
formatAuthenticationAlert,
enrollPasskey,
revokeActiveDevice,
revokePasskey,