profoundjs
Version:
Profound.js Framework and Server
374 lines (341 loc) • 15.9 kB
JavaScript
// @ts-nocheck
/* eslint-disable */
;
(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> (${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) : "";
}
})();