Added authentication
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AOC Events</title>
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<script src="https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
@@ -15,6 +16,7 @@
|
||||
<p class="lede">Filter Microsoft Entra audit events by user, app, time, action, and action type.</p>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<button id="authBtn" class="ghost" aria-label="Login">Login</button>
|
||||
<button id="fetchBtn" aria-label="Fetch latest audit logs">Fetch new</button>
|
||||
<button id="refreshBtn" aria-label="Refresh events">Refresh</button>
|
||||
</div>
|
||||
@@ -94,6 +96,7 @@
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const fetchBtn = document.getElementById('fetchBtn');
|
||||
const clearBtn = document.getElementById('clearBtn');
|
||||
const authBtn = document.getElementById('authBtn');
|
||||
const modal = document.getElementById('modal');
|
||||
const modalBody = document.getElementById('modalBody');
|
||||
const closeModal = document.getElementById('closeModal');
|
||||
@@ -101,6 +104,11 @@
|
||||
let currentPage = 1;
|
||||
let totalItems = 0;
|
||||
let pageSize = 50;
|
||||
let authConfig = null;
|
||||
let msalInstance = null;
|
||||
let account = null;
|
||||
let accessToken = null;
|
||||
let authScopes = [];
|
||||
const lists = {
|
||||
actor: document.getElementById('actorOptions'),
|
||||
service: document.getElementById('serviceOptions'),
|
||||
@@ -114,9 +122,9 @@
|
||||
return isNaN(date.getTime()) ? '' : date.toISOString();
|
||||
};
|
||||
|
||||
async function loadEvents() {
|
||||
const params = new URLSearchParams();
|
||||
const data = new FormData(form);
|
||||
async function loadEvents() {
|
||||
const params = new URLSearchParams();
|
||||
const data = new FormData(form);
|
||||
['actor', 'service', 'operation', 'result', 'search'].forEach((key) => {
|
||||
const val = data.get(key)?.trim();
|
||||
if (val) params.append(key, val);
|
||||
@@ -135,12 +143,17 @@
|
||||
}
|
||||
params.append('page', currentPage);
|
||||
|
||||
status.textContent = 'Loading events…';
|
||||
eventsContainer.innerHTML = '';
|
||||
count.textContent = '';
|
||||
status.textContent = 'Loading events…';
|
||||
eventsContainer.innerHTML = '';
|
||||
count.textContent = '';
|
||||
|
||||
if (authConfig?.auth_enabled && !accessToken) {
|
||||
status.textContent = 'Please sign in to load events.';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/events?${params.toString()}`, { headers: { Accept: 'application/json' } });
|
||||
const res = await fetch(`/api/events?${params.toString()}`, { headers: { Accept: 'application/json', ...authHeader() } });
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
throw new Error(`Request failed: ${res.status} ${msg}`);
|
||||
@@ -159,10 +172,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchLogs() {
|
||||
status.textContent = 'Fetching latest audit logs…';
|
||||
try {
|
||||
const res = await fetch('/api/fetch-audit-logs');
|
||||
async function fetchLogs() {
|
||||
status.textContent = 'Fetching latest audit logs…';
|
||||
if (authConfig?.auth_enabled && !accessToken) {
|
||||
status.textContent = 'Please sign in first.';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch('/api/fetch-audit-logs', { headers: authHeader() });
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
throw new Error(`Fetch failed: ${res.status} ${msg}`);
|
||||
@@ -177,8 +194,9 @@
|
||||
}
|
||||
|
||||
async function loadFilterOptions() {
|
||||
if (authConfig?.auth_enabled && !accessToken) return;
|
||||
try {
|
||||
const res = await fetch('/api/filter-options');
|
||||
const res = await fetch('/api/filter-options', { headers: authHeader() });
|
||||
if (!res.ok) return;
|
||||
const opts = await res.json();
|
||||
const setOptions = (el, values) => {
|
||||
@@ -266,6 +284,112 @@
|
||||
if (next) next.addEventListener('click', () => { if (currentPage < totalPages) { currentPage += 1; loadEvents(); } });
|
||||
}
|
||||
|
||||
function authHeader() {
|
||||
return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
|
||||
}
|
||||
|
||||
const pickToken = (res) => (res ? (res.accessToken || res.idToken || null) : null);
|
||||
|
||||
async function initAuth() {
|
||||
try {
|
||||
const res = await fetch('/api/config/auth');
|
||||
authConfig = await res.json();
|
||||
} catch {
|
||||
authConfig = { auth_enabled: false };
|
||||
}
|
||||
|
||||
if (!authConfig?.auth_enabled) {
|
||||
loginBtn.classList.add('hidden');
|
||||
logoutBtn.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof msal === 'undefined' || !msal.PublicClientApplication) {
|
||||
status.textContent = 'Login library failed to load. Please check network or CDN.';
|
||||
return;
|
||||
}
|
||||
|
||||
const tenantId = authConfig.tenant_id;
|
||||
const clientId = authConfig.client_id;
|
||||
const baseScope = authConfig.scope || "";
|
||||
authScopes = Array.from(
|
||||
new Set(
|
||||
['openid', 'profile', 'email', ...baseScope.split(/[ ,]+/).filter(Boolean)]
|
||||
)
|
||||
);
|
||||
const authority = `https://login.microsoftonline.com/${tenantId}`;
|
||||
const redirectUri = window.location.origin;
|
||||
|
||||
msalInstance = new msal.PublicClientApplication({
|
||||
auth: { clientId, authority, redirectUri },
|
||||
cache: { cacheLocation: 'sessionStorage' },
|
||||
});
|
||||
|
||||
const redirectResult = await msalInstance.handleRedirectPromise().catch(() => null);
|
||||
if (redirectResult) {
|
||||
account = redirectResult.account;
|
||||
msalInstance.setActiveAccount(account);
|
||||
accessToken = pickToken(redirectResult);
|
||||
} else {
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
if (accounts.length) {
|
||||
account = accounts[0];
|
||||
msalInstance.setActiveAccount(account);
|
||||
accessToken = await acquireToken(authScopes);
|
||||
}
|
||||
}
|
||||
|
||||
updateAuthButtons();
|
||||
if (accessToken) {
|
||||
await loadFilterOptions();
|
||||
await loadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
async function acquireToken(scopes) {
|
||||
if (!msalInstance || !account) return null;
|
||||
const request = { scopes: scopes && scopes.length ? scopes : ['openid', 'profile', 'email'], account };
|
||||
try {
|
||||
const res = await msalInstance.acquireTokenSilent(request);
|
||||
return pickToken(res);
|
||||
} catch {
|
||||
const res = await msalInstance.acquireTokenPopup(request);
|
||||
return pickToken(res);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAuthButtons() {
|
||||
const loggedIn = !!account;
|
||||
if (authConfig?.auth_enabled) {
|
||||
authBtn.textContent = loggedIn ? 'Logout' : 'Login';
|
||||
}
|
||||
if (loggedIn) {
|
||||
// Refresh token silently on page load if needed.
|
||||
acquireToken(authScopes).then((t) => { if (t) accessToken = t; }).catch(() => {});
|
||||
status.textContent = '';
|
||||
} else if (authConfig?.auth_enabled) {
|
||||
status.textContent = 'Please log in to view events.';
|
||||
}
|
||||
}
|
||||
|
||||
authBtn.addEventListener('click', async () => {
|
||||
if (!authConfig?.auth_enabled || !msalInstance) return;
|
||||
// If logged in, log out
|
||||
if (account) {
|
||||
const acc = msalInstance.getActiveAccount();
|
||||
accessToken = null;
|
||||
account = null;
|
||||
updateAuthButtons();
|
||||
if (acc) {
|
||||
await msalInstance.logoutPopup({ account: acc });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const scopes = authScopes && authScopes.length ? authScopes : ['openid', 'profile', 'email'];
|
||||
status.textContent = 'Redirecting to sign in...';
|
||||
msalInstance.loginRedirect({ scopes });
|
||||
});
|
||||
|
||||
closeModal.addEventListener('click', () => modal.classList.add('hidden'));
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) modal.classList.add('hidden');
|
||||
@@ -286,8 +410,7 @@
|
||||
loadEvents();
|
||||
});
|
||||
|
||||
loadFilterOptions();
|
||||
loadEvents();
|
||||
initAuth();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user