UNPKG

profoundjs

Version:

Profound.js Framework and Server

374 lines (341 loc) 15.9 kB
// @ts-nocheck /* eslint-disable */ "use strict"; (function() { const baseUrl = document.location.href.replace(document.location.hash, "").replace(document.location.search, ""); let schemes = {}; let schemeKeys = []; fetch(baseUrl + "explorer-schemes") .then(function(r) { return r.json(); }) .then(function(data) { schemes = data.securitySchemes || {}; schemeKeys = Object.keys(schemes); if (schemeKeys.length === 0) { document.getElementById("auth-content").innerHTML = '<p style="padding: 20px;">No security schemes are configured. Please configure security schemes in your openapi.json.</p>'; return; } renderSchemes(); }) .catch(function(err) { document.getElementById("auth-content").innerHTML = '<p style="padding: 20px; color: red;">Failed to load security schemes: ' + err.message + '</p>'; }); function renderSchemes() { let html = ""; for (let i = 0; i < schemeKeys.length; i++) { html += renderScheme(schemeKeys[i], schemes[schemeKeys[i]]); } // Shared message area + unified Authorize button. html += '<div class="auth-shared"><div class="error-message" id="auth-shared-error"></div><div class="success-message" id="auth-shared-success"></div><div class="auth-btn-wrapper"><button class="btn modal-btn auth authorize button" id="btn-authorize-all">Authorize</button><button class="btn modal-btn auth btn-done button" id="btn-logout-all" style="display:none;">Logout</button></div></div>'; document.getElementById("auth-content").innerHTML = html; for (let j = 0; j < schemeKeys.length; j++) { attachSchemeEvents(schemeKeys[j], schemes[schemeKeys[j]]); } attachUnifiedEvents(); } function renderScheme(name, scheme) { const typeLabel = getTypeLabel(scheme); const inputs = getInputs(name, scheme); const description = scheme.description ? "<p>" + escapeHtml(scheme.description) + "</p>" : ""; // OAuth2 keeps its own per-scheme Authorize button because of the popup flow. return `<div class="auth-container" id="auth-${name}"><form onsubmit="return false;"><div><h4><code>${escapeHtml(name)}</code>&nbsp; (${escapeHtml(typeLabel)})</h4></div>${description}${inputs}<div class="error-message" id="error-${name}"></div><div class="success-message" id="success-${name}"></div></form></div>`; } function getTypeLabel(scheme) { if (scheme.type === "apiKey") return "apiKey"; if (scheme.type === "http") { if (scheme["x-ibmi"] === true) return "http, IBMi"; return "http, " + capitalize(scheme.scheme || "basic"); } if (scheme.type === "oauth2") return "OAuth2"; return scheme.type; } function getInputs(name, scheme) { if (scheme.type === "apiKey") { return `<div class="scheme-info">Name: <code>${escapeHtml(scheme.name || "X-API-Key")}</code>, In: <code>${escapeHtml(scheme.in || "header")}</code></div><div><label for="input-${name}">Value:</label><input type="text" id="input-${name}" /></div>`; } if (scheme.type === "http" && (scheme.scheme || "").toLowerCase() === "bearer") { const bf = scheme.bearerFormat ? `<div class="scheme-info">Bearer format: <code>${escapeHtml(scheme.bearerFormat)}</code></div>` : ""; return `${bf}<div><label for="input-${name}">Value:</label><input type="text" id="input-${name}" /></div>`; } if (scheme.type === "http") { return `<div><label for="input-user-${name}">Username:</label><input type="text" id="input-user-${name}" /></div><div><label for="input-pass-${name}">Password:</label><input type="password" id="input-pass-${name}" /></div>`; } if (scheme.type === "oauth2") { const flow = (scheme.flows && scheme.flows.authorizationCode) || {}; const scopeNames = Object.keys(flow.scopes || {}); let scopesUI = ""; if (scopeNames.length) { const items = scopeNames.map(function(s) { return `<div class="scope-row"><label><input type="checkbox" class="scope-${name}" value="${escapeHtml(s)}" checked /> <code>${escapeHtml(s)}</code> <span class="scope-desc">${escapeHtml(flow.scopes[s] || "")}</span></label></div>`; }).join(""); scopesUI = `<div class="scopes"><h2>Scopes: <a href="javascript:void(0)" class="scope-all-${name}">select all</a> <a href="javascript:void(0)" class="scope-none-${name}">select none</a></h2>${items}</div>`; } return `<p>Authorization URL: <code>${escapeHtml(flow.authorizationUrl || "")}</code></p>` + `<p>Token URL: <code>${escapeHtml(flow.tokenUrl || "")}</code></p>` + `<p class="flow">Flow: <code>authorizationCode</code></p>` + `<div><label for="input-client-id-${name}">client_id:</label><input type="text" id="input-client-id-${name}" /></div>` + `<div><label for="input-client-secret-${name}">client_secret:</label><input type="password" id="input-client-secret-${name}" /></div>` + scopesUI; } return ""; } function attachSchemeEvents(name, scheme) { // OAuth2 keeps the "select all" / "select none" scope link wiring; the // popup itself is now triggered by the unified Authorize button. if (scheme.type !== "oauth2") return; const allLink = document.querySelector(".scope-all-" + name); const noneLink = document.querySelector(".scope-none-" + name); if (allLink) allLink.addEventListener("click", function() { document.querySelectorAll(".scope-" + name).forEach(function(cb) { cb.checked = true; }); }); if (noneLink) noneLink.addEventListener("click", function() { document.querySelectorAll(".scope-" + name).forEach(function(cb) { cb.checked = false; }); }); } function attachUnifiedEvents() { const authBtn = document.getElementById("btn-authorize-all"); const logoutBtn = document.getElementById("btn-logout-all"); authBtn.addEventListener("click", function() { hideSharedMessages(); for (let i = 0; i < schemeKeys.length; i++) hideMessages(schemeKeys[i]); const submitted = collectFilledSchemes(); const oauthInUse = findFilledOAuth2(); if (Object.keys(submitted).length === 0 && !oauthInUse) { showSharedError("Provide credentials for at least one authentication scheme."); return; } authBtn.disabled = true; authBtn.textContent = "Authorizing..."; if (oauthInUse) { runOAuth2Flow(oauthInUse.name, oauthInUse.scheme, submitted, authBtn); } else { sendExplorerAuth({ schemes: submitted }, authBtn); } }); logoutBtn.addEventListener("click", function() { fetch(baseUrl + "explorer-logout", { method: "POST" }) .then(function() { logoutBtn.style.display = "none"; authBtn.style.display = ""; authBtn.disabled = false; authBtn.textContent = "Authorize"; for (let i = 0; i < schemeKeys.length; i++) enableInputs(schemeKeys[i]); hideSharedMessages(); }); }); } // Walk every non-OAuth2 scheme and return { schemeName: { authType, ...creds } } for filled ones. function collectFilledSchemes() { const out = {}; for (let i = 0; i < schemeKeys.length; i++) { const name = schemeKeys[i]; const scheme = schemes[name]; if (scheme.type === "oauth2") continue; const body = buildAuthBody(name, scheme, /*silent*/ true); if (body) out[name] = body; } return out; } // Find the first OAuth2 scheme with a non-empty client_id (the user wants to use it). function findFilledOAuth2() { for (let i = 0; i < schemeKeys.length; i++) { const name = schemeKeys[i]; const scheme = schemes[name]; if (scheme.type !== "oauth2") continue; const el = document.getElementById("input-client-id-" + name); if (el && el.value.trim()) return { name, scheme }; } return null; } function sendExplorerAuth(body, authBtn) { fetch(baseUrl + "explorer-auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }) .then(function(r) { if (!r.ok) return r.json().then(function(d) { throw new Error(d.error || "Authentication failed."); }); return r.json(); }) .then(function(data) { showSharedSuccess("Authorized as " + data.user + ". Loading Explorer..."); authBtn.style.display = "none"; for (let i = 0; i < schemeKeys.length; i++) disableInputs(schemeKeys[i]); setTimeout(function() { window.location.reload(); }, 800); }) .catch(function(err) { showSharedError(err.message); authBtn.disabled = false; authBtn.textContent = "Authorize"; }); } // OAuth2 authorization-code flow — popup + token exchange. The resulting // bearer token is added into the combined `otherSchemes` map and submitted // through the same /explorer-auth endpoint so OAuth2 can be one of several // schemes in an AND-style security[] group. function runOAuth2Flow(name, scheme, otherSchemes, authBtn) { const flow = (scheme.flows && scheme.flows.authorizationCode) || {}; const clientId = document.getElementById("input-client-id-" + name).value.trim(); const clientSecret = document.getElementById("input-client-secret-" + name).value; if (!clientId) { showSharedError("client_id is required for OAuth2."); resetUnifiedButton(authBtn); return; } if (!flow.authorizationUrl || !flow.tokenUrl) { showSharedError("OAuth2 scheme is missing authorizationUrl or tokenUrl."); resetUnifiedButton(authBtn); return; } const redirectUri = baseUrl + "oauth2-redirect.html"; const stateBytes = new Uint8Array(16); crypto.getRandomValues(stateBytes); const state = Array.from(stateBytes).map(function(b) { return b.toString(16).padStart(2, "0"); }).join(""); const selectedScopes = Array.prototype.slice.call(document.querySelectorAll(".scope-" + name + ":checked")) .map(function(cb) { return cb.value; }); const scopes = selectedScopes.join(" "); const authUrl = flow.authorizationUrl + (flow.authorizationUrl.indexOf("?") >= 0 ? "&" : "?") + "response_type=code" + "&client_id=" + encodeURIComponent(clientId) + "&redirect_uri=" + encodeURIComponent(redirectUri) + "&state=" + encodeURIComponent(state) + (scopes ? "&scope=" + encodeURIComponent(scopes) : ""); let popupWatcher = null; let codeReceived = false; function stopWatcher() { if (popupWatcher) { clearInterval(popupWatcher); popupWatcher = null; } } function finish(err) { stopWatcher(); try { window.swaggerUIRedirectOauth2 = null; } catch (_) {} if (err) { showSharedError(err); resetUnifiedButton(authBtn); } } function exchangeCode(code) { const form = new URLSearchParams(); form.set("grant_type", "authorization_code"); form.set("code", code); form.set("redirect_uri", redirectUri); form.set("client_id", clientId); if (clientSecret) form.set("client_secret", clientSecret); fetch(flow.tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" }, body: form.toString() }) .then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); }) .then(function(res) { if (!res.ok || !res.data.access_token) { throw new Error(res.data.error_description || res.data.error || "Token exchange failed."); } // Merge the OAuth2 token into the combined schemes payload and submit. const combined = Object.assign({}, otherSchemes); combined[name] = { authType: "bearer", value: res.data.access_token }; try { window.swaggerUIRedirectOauth2 = null; } catch (_) {} sendExplorerAuth({ schemes: combined }, authBtn); }) .catch(function(err) { finish(err.message); }); } window.swaggerUIRedirectOauth2 = { state: state, redirectUrl: redirectUri, auth: { name: name, schema: { get: function(key) { return key === "flow" ? "authorizationCode" : null; } }, code: null }, callback: function(data) { if (data && data.auth && data.auth.code) { codeReceived = true; stopWatcher(); exchangeCode(data.auth.code); } else finish("OAuth2 authorization failed: no code returned"); }, errCb: function(errInfo) { codeReceived = true; finish((errInfo && errInfo.message) || "OAuth2 authorization error"); } }; const popup = window.open(authUrl, "pjs-oauth2", "width=600,height=700"); if (!popup) { finish("Popup blocked. Allow popups for this site and try again."); return; } popupWatcher = setInterval(function() { if (popup.closed && !codeReceived) { stopWatcher(); try { window.swaggerUIRedirectOauth2 = null; } catch (_) {} resetUnifiedButton(authBtn); } }, 500); } function resetUnifiedButton(authBtn) { authBtn.disabled = false; authBtn.textContent = "Authorize"; } function buildAuthBody(name, scheme, silent) { if (scheme.type === "apiKey") { const val = document.getElementById("input-" + name).value.trim(); if (!val) return null; return { authType: "apiKey", headerName: scheme.name || "X-API-Key", inLocation: scheme.in || "header", value: val }; } else if (scheme.type === "http" && (scheme.scheme || "").toLowerCase() === "bearer") { const token = document.getElementById("input-" + name).value.trim(); if (!token) return null; return { authType: "bearer", value: token }; } else if (scheme.type === "http") { const ibmi = scheme["x-ibmi"] === true; const user = document.getElementById("input-user-" + name).value.trim(); const pass = document.getElementById("input-pass-" + name).value; if (!user && !pass) return null; if (!user) { if (!silent) showError(name, "Username is required."); return null; } if (!pass) { if (!silent) showError(name, "Password is required."); return null; } return { authType: ibmi ? "ibmi" : "basic", username: user, password: pass }; } return null; } function disableInputs(name) { const inputs = document.querySelectorAll("#auth-" + name + " input"); for (let i = 0; i < inputs.length; i++) inputs[i].disabled = true; } function enableInputs(name) { const inputs = document.querySelectorAll("#auth-" + name + " input"); for (let i = 0; i < inputs.length; i++) { inputs[i].disabled = false; inputs[i].value = ""; } } function showError(name, msg) { const el = document.getElementById("error-" + name); if (!el) return; el.textContent = msg; el.style.display = "block"; } function hideMessages(name) { const e = document.getElementById("error-" + name); const s = document.getElementById("success-" + name); if (e) e.style.display = "none"; if (s) s.style.display = "none"; } function showSharedError(msg) { const el = document.getElementById("auth-shared-error"); el.textContent = msg; el.style.display = "block"; } function showSharedSuccess(msg) { const el = document.getElementById("auth-shared-success"); el.textContent = msg; el.style.display = "block"; } function hideSharedMessages() { document.getElementById("auth-shared-error").style.display = "none"; document.getElementById("auth-shared-success").style.display = "none"; } function escapeHtml(str) { const div = document.createElement("div"); div.appendChild(document.createTextNode(str || "")); return div.innerHTML; } function capitalize(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : ""; } })();