@hawtio/react
Version:
A Hawtio reimplementation based on TypeScript + React.
1,322 lines (1,258 loc) • 729 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6;
var _chunkBLJGIIMVjs = require('./chunk-BLJGIIMV.js');
var _chunkWYFZRJ4Cjs = require('./chunk-WYFZRJ4C.js');
var _chunkBJ6TSPQKjs = require('./chunk-BJ6TSPQK.js');
var _chunkBHIEXRGKjs = require('./chunk-BHIEXRGK.js');
// src/help/registry.ts
var HelpRegistry = (_class = class {constructor() { _class.prototype.__init.call(this); }
__init() {this.helps = {}}
add(id, title, content, order9 = 100) {
if (this.helps[id]) {
throw new Error(`Help '${id}' already registered`);
}
this.helps[id] = { id, title, content, order: order9 };
}
getHelps() {
return Object.values(this.helps).sort((a, b) => a.order - b.order);
}
reset() {
this.helps = {};
}
}, _class);
var helpRegistry = new HelpRegistry();
// src/plugins/auth/keycloak/help.md
var help_default = "## Keycloak\n\nThe Keycloak plugin allows you to integrate Hawtio console with Keycloak authentication.\n";
// src/util/globals.ts
var log2 = _chunkBHIEXRGKjs.Logger.get("hawtio-util");
// src/util/fetch.ts
async function fetchPath(path, callback) {
try {
const res = await fetch(path);
if (!res.ok) {
log2.error("Failed to fetch", path, ":", res.status, res.statusText);
return callback.error();
}
const data = await res.text();
return callback.success(data);
} catch (err) {
log2.error("Failed to fetch", path, ":", err);
return callback.error();
}
}
// src/plugins/auth/keycloak/keycloak-service.ts
var _keycloakjs = require('keycloak-js'); var _keycloakjs2 = _interopRequireDefault(_keycloakjs);
// src/plugins/auth/keycloak/globals.ts
var pluginName = "hawtio-auth-keycloak";
var log3 = _chunkBHIEXRGKjs.Logger.get(pluginName);
var PATH_KEYCLOAK_ENABLED = "keycloak/enabled";
var PATH_KEYCLOAK_CLIENT_CONFIG = "keycloak/client-config";
var PATH_KEYCLOAK_VALIDATE = "keycloak/validate-subject-matches";
// src/plugins/auth/keycloak/keycloak-service.ts
var pluginName2 = "hawtio-keycloak";
var AUTH_METHOD = "keycloak";
var log4 = _chunkBHIEXRGKjs.Logger.get(pluginName2);
var KEYCLOAK_TOKEN_MINIMUM_VALIDITY = 5;
var KeycloakService = (_class2 = class {
// promises created during construction
// configuration from Hawtio backend - should include provider URL and realm. defined in keycloak.js
// Keycloak is enabled when /keycloak/client-config endpoint returns `true`
// Keycloak instance - from keycloak.js which performs all Keycloak interaction
// promise related to logged-in user. Contains JWT access_token from Keycloak and information about
// user from id_token.
// when user is logged in, the credentials are represented by the token (usually JWT), which has to
// be sent with every HTTP request using built-in fetch, which we wrap
constructor() {;_class2.prototype.__init2.call(this);
this.enabled = this.loadKeycloakEnabled();
this.config = this.loadKeycloakConfig();
this.keycloak = this.createKeycloak();
this.userProfile = this.loadUserProfile();
this.originalFetch = fetch;
this.originalFetch = this.originalFetch.bind(window);
}
/**
* Simply checks if Keycloak plugin should be enabled in _native_ mode (using `keycloak.js` library)
* using `/keycloak/enabled` endpoint
* @private
*/
async loadKeycloakEnabled() {
_chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 0 /* started */, "config");
return fetch(PATH_KEYCLOAK_ENABLED).then((response) => response.ok && response.status == 200 ? response.text() : null).then((data) => {
const enabled = data ? data.trim() === "true" : false;
if (!enabled) {
_chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config");
}
log4.debug("Keycloak enabled:", enabled);
return enabled;
}).catch(() => {
_chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config");
return false;
});
}
/**
* Load Keycloak Client configuration required by `keycloak.js`
* @private
*/
async loadKeycloakConfig() {
const enabled = await this.enabled;
if (!enabled) {
return null;
}
return fetch(PATH_KEYCLOAK_CLIENT_CONFIG).then((response) => response.ok && response.status == 200 ? response.json() : null).then((json) => {
if (json) {
log4.debug("Loaded", PATH_KEYCLOAK_CLIENT_CONFIG, ":", json);
return json;
} else {
_chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config");
}
return null;
}).catch(() => {
_chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config");
return null;
});
}
/**
* Create Keycloak object to communicate with Identity Provider
* @private
*/
async createKeycloak() {
const enabled = await this.enabled;
const config = await this.config;
if (!enabled || !config) {
log4.debug("Keycloak disabled");
return null;
}
_chunkBHIEXRGKjs.configManager.configureAuthenticationMethod({
method: AUTH_METHOD,
login: this.keycloakLogin
}).then(() => {
_chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 2 /* finished */, "config");
});
return new (0, _keycloakjs2.default)(config);
}
__init2() {this.keycloakLogin = async (silent = false) => {
const keycloak2 = await this.keycloak;
if (!keycloak2) {
return 1 /* configuration_error */;
}
if (!window.isSecureContext) {
log4.error("Can't perform Keycloak authentication in non-secure context");
return 3 /* security_context_error */;
}
const options = {};
if (silent) {
options.prompt = "none";
}
await keycloak2.login(options);
return 0 /* ok */;
}}
async loadUserProfile() {
const keycloak2 = await this.keycloak;
if (!keycloak2) {
return null;
}
const initOptions = await this.getKeycloakInitOptions();
try {
const authenticated = await keycloak2.init(initOptions);
log4.info("Initialised Keycloak: authenticated =", authenticated);
if (!authenticated) {
return null;
}
try {
const profile = await keycloak2.loadUserProfile();
log4.debug("Loaded Keycloak profile:", profile);
profile.token = keycloak2.token;
return profile;
} catch (error) {
log4.error("Failed to load user profile:", error);
}
} catch (error) {
log4.error("Failed to initialise Keycloak:", error);
}
return null;
}
/**
* Options for `Keycloak.init()`
* @private
*/
async getKeycloakInitOptions() {
const config = await this.config;
const pkceMethod = _optionalChain([config, 'optionalAccess', _2 => _2.pkceMethod]);
return {
// safer than 'query'
responseMode: "fragment",
// means responseType = 'code'
flow: "standard",
// check silent login by default
onLoad: "check-sso",
// onLoad: 'login-required',
pkceMethod,
// this may be reset to false by Keycloak in check3pCookiesSupported()
// reason may be strict cookie policy. But we'll use this option for silent SSO login
checkLoginIframe: true
};
}
isKeycloakEnabled() {
return this.enabled;
}
registerUserHooks(helpRegistration) {
const fetchUser = async (resolve) => {
const keycloak2 = await this.keycloak;
const userProfile = await this.userProfile;
if (!keycloak2 || !userProfile) {
return { isIgnore: true, isError: false, loginMethod: AUTH_METHOD };
}
if (userProfile.username && userProfile.token) {
resolve({ username: userProfile.username, isLogin: true, loginMethod: AUTH_METHOD });
_chunkBHIEXRGKjs.userService.setToken(userProfile.token);
}
this.setupFetch();
helpRegistration();
return { isIgnore: false, isError: false, loginMethod: AUTH_METHOD };
};
_chunkBHIEXRGKjs.userService.addFetchUserHook(AUTH_METHOD, fetchUser);
const logout = async () => {
const keycloak2 = await this.keycloak;
if (!keycloak2) {
return false;
}
const config = await this.config;
let logoutUri = _optionalChain([config, 'optionalAccess', _3 => _3.logoutUri]);
if (logoutUri) {
if (!URL.canParse(logoutUri)) {
logoutUri = new URL(logoutUri, document.baseURI).href;
}
}
log4.info("Log out Keycloak");
try {
await keycloak2.logout({ redirectUri: logoutUri });
} catch (error) {
log4.error("Error logging out Keycloak:", error);
}
return true;
};
_chunkBHIEXRGKjs.userService.addLogoutHook(AUTH_METHOD, logout);
}
async setupFetch() {
const keycloak2 = await this.keycloak;
const config = await this.config;
if (!keycloak2 || !config) {
return;
}
log4.debug("Intercept Fetch API to attach Keycloak token to authorization header");
const { fetch: originalFetch } = window;
window.fetch = async (input, init) => {
const logPrefix = "Fetch -";
log4.debug(logPrefix, "Fetch intercepted for Keycloak authentication");
if (!keycloak2.authenticated || keycloak2.isTokenExpired(KEYCLOAK_TOKEN_MINIMUM_VALIDITY)) {
log4.debug(logPrefix, "Try to update token for request:", input);
return new Promise((resolve, reject) => {
this.updateToken(
(token2) => {
if (token2) {
log4.debug(logPrefix, "Keycloak token refreshed. Set new value to userService");
_chunkBHIEXRGKjs.userService.setToken(token2);
}
log4.debug(logPrefix, "Re-sending request after successfully updating Keycloak token:", input);
resolve(fetch(input, init));
},
() => {
log4.debug(logPrefix, "Logging out due to token update failed");
_chunkBHIEXRGKjs.userService.logout();
reject();
}
);
});
}
init = { ...init };
if (config.jaas) {
if (keycloak2.profile && keycloak2.profile.username && keycloak2.token) {
init.headers = {
...init.headers,
Authorization: _chunkBJ6TSPQKjs.basicAuthHeaderValue.call(void 0, keycloak2.profile.username, keycloak2.token)
};
} else {
log4.error(logPrefix, "Keycloak username or token not found in JAAS mode:", keycloak2.profile, keycloak2.token);
}
} else {
init.headers = {
...init.headers,
Authorization: `Bearer ${keycloak2.token}`
};
}
const token = _chunkBJ6TSPQKjs.getCookie.call(void 0, "XSRF-TOKEN");
if (token) {
log4.debug(logPrefix, "Set XSRF token header from cookies");
init.headers = {
...init.headers,
"X-XSRF-TOKEN": token
};
}
return originalFetch(input, init);
};
}
async updateToken(successFn, errorFn) {
const keycloak2 = await this.keycloak;
if (!keycloak2) {
return;
}
keycloak2.updateToken(KEYCLOAK_TOKEN_MINIMUM_VALIDITY).then((refreshed) => {
if (refreshed) {
const token = keycloak2.token;
if (token) {
successFn(token);
}
} else {
log4.debug("Token is still valid");
}
}).catch((reason) => {
log4.error("Couldn't update token:", reason);
_optionalChain([errorFn, 'optionalCall', _4 => _4()]);
});
}
// TODO: validate-subject-matches
validateSubjectMatches(user) {
const url = `${PATH_KEYCLOAK_VALIDATE}?keycloakUser=${encodeURIComponent(user)}`;
return fetchPath(url, {
success: (data) => {
log4.debug("Got response for validate subject matches:", data);
return JSON.parse(data);
},
error: () => false
});
}
}, _class2);
var keycloakService = new KeycloakService();
// src/plugins/auth/keycloak/index.ts
var keycloak = () => {
let helpRegistered = false;
keycloakService.registerUserHooks(() => {
if (!helpRegistered) {
helpRegistry.add("keycloak", "Keycloak", help_default, 21);
helpRegistered = true;
}
});
};
// src/plugins/auth/oidc/help.md
var help_default2 = "## OpenID Connect\n\nThe OIDC plugin allows you to integrate Hawtio console with generic OIDC provider.\n";
// src/plugins/auth/oidc/oidc-service.ts
var _jwtdecode = require('jwt-decode');
var _oauth4webapi = require('oauth4webapi'); var oidc = _interopRequireWildcard(_oauth4webapi);
var pluginName3 = "hawtio-oidc";
var AUTH_METHOD2 = "oidc";
var log5 = _chunkBHIEXRGKjs.Logger.get(pluginName3);
var clientAuth = (_as, client, parameters, _headers) => {
parameters.set("client_id", client.client_id);
return Promise.resolve();
};
var OidcService = (_class3 = class {
// promises created during construction
// OIDC plugin is enabled when there's any configuration with a provider URL in the config - may not be reachable though
// configuration from Hawtio backend - should include provider URL
// we keep an array (usually 1-elem) for all OIDC providers - one promise for all configs (fetched from a single endpoint)
// OIDC metadata may come with its config. When not provided we have to get it from .well-known/openid-configuration
// if we can't access it during initialization, we have to try on login
// this is an array of promises, because each OidcConfig has own metadata which may be fetched at different time
__init3() {this.oidcMetadata = []}
// promise related to logged-in user. contains user name and tokens. This promise is resolved
// after completing OAuth2 authorization flow or retrieving existing user using OIDC session
// if there's no information about the user however we resolve this promise with null and we
// don't start Authorization Flow - it's started only on user request
// we have to know which particular provider was used for OIDC login
__init4() {this.selectedProvider = -1}
// when user is logged in, the credentials are represented by the token (usually JWT), which has to
// be sent with every HTTP request using built-in fetch, which we wrap
constructor() {;_class3.prototype.__init3.call(this);_class3.prototype.__init4.call(this);_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);
_chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 0 /* started */, "config");
this.config = fetch("auth/config/oidc").then((response) => {
if (response.ok && response.status == 200) {
return response.json();
} else if (response.status == 404) {
return fetch("auth/config").then((response2) => response2.ok && response2.status == 200 ? response2.json() : null);
} else {
return null;
}
}).then((json) => {
return !json ? [] : Array.isArray(json) ? json : [json];
}).catch(() => {
_chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 1 /* skipped */, "config");
return [];
});
this.enabled = this.isOidcEnabled();
Promise.all([this.enabled, this.config]).then(([enabled, config]) => {
if (enabled) {
this.oidcMetadata = [];
config.forEach((c, idx) => {
const mdPromise = this.fetchOidcMetadata(c).then((md) => {
return this.processOidcMetadata(c, md, idx, true);
}).catch((_e) => {
return null;
});
this.oidcMetadata.push(mdPromise);
});
Promise.all(this.oidcMetadata).then(() => {
_chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 2 /* finished */, "config");
});
} else {
_chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 1 /* skipped */, "config");
this.oidcMetadata = [];
}
});
this.userInfo = this.initialize();
this.originalFetch = fetch;
this.originalFetch = this.originalFetch.bind(window);
}
/**
* This should happen during initialization, but when OIDC is enabled and we can't fetch OIDC metadata initially,
* we'll be trying to do it during login until we get the metadata.
* @param c
* @param md
* @param idx
* @param initial
* @private
*/
async processOidcMetadata(c, md, idx, initial = false) {
if (md) {
c["openid-configuration"] = md;
}
if (initial) {
if (!c.position) {
c.position = idx;
}
c.login = this.oidcLoginFunction(idx);
_chunkBHIEXRGKjs.configManager.configureAuthenticationMethod(c);
}
return md;
}
/**
* OIDC is enabled when configuration has `oidc` method and `provider` is specified
* @private
*/
async isOidcEnabled() {
const cfg = await this.config;
if (_optionalChain([cfg, 'optionalAccess', _5 => _5.length]) == 0) {
return false;
}
return cfg.find((c) => _optionalChain([c, 'optionalAccess', _6 => _6.method]) === AUTH_METHOD2 && _optionalChain([c, 'optionalAccess', _7 => _7.provider]) != null) != void 0;
}
/**
* Returns information about target IDP - either from initial configuration (prepared at server side by
* `/auth/config/oidc` endpoint) or from OIDC metadata using additional request. Called only when OIDC
* method is enabled
* @param cfg
* @private
*/
async fetchOidcMetadata(cfg) {
let res = null;
if (!cfg) {
return null;
}
if (cfg["openid-configuration"]) {
log5.info("Using pre-fetched openid-configuration");
return cfg["openid-configuration"];
} else {
log5.info("Fetching .well-known/openid-configuration");
const cfgUrl = new URL(cfg.provider);
res = await oidc.discoveryRequest(cfgUrl, {
[oidc.allowInsecureRequests]: true
// to also allow http requests (it's not "trust-all" flag)
}).catch((e) => {
log5.error("Failed OIDC discovery request", e);
});
if (!res || !res.ok) {
return null;
}
return oidc.processDiscoveryResponse(cfgUrl, res);
}
}
/**
* OIDC initialization - we can check existing _state_ which is available in URI (after redirect from IdP)
* and/or in localStorage (state kept in between cross origin requests)
* @private
*/
async initialize() {
const enabled = await this.enabled;
const configArray = await this.config;
if (configArray.length == 0 || !enabled) {
return null;
}
const selectedIdx = localStorage.getItem("hawtio-oidc-login-idx");
if (!selectedIdx) {
return null;
}
const idx = parseInt(selectedIdx);
if (idx < 0 || idx >= configArray.length) {
return null;
}
if (idx >= configArray.length || idx >= this.oidcMetadata.length) {
throw new Error(`Invalid OIDC authentication configuration with idx=${idx}`);
}
this.selectedProvider = idx;
const config = configArray[idx];
const as = await this.oidcMetadata[idx];
if (!as || !config) {
return null;
}
let urlParams = null;
if (config.response_mode === "fragment") {
if (window.location.hash && window.location.hash.length > 0) {
urlParams = new URLSearchParams(window.location.hash.substring(1));
}
} else if (config.response_mode === "query") {
if (window.location.search || window.location.search.length > 0) {
urlParams = new URLSearchParams(window.location.search.substring(1));
}
}
const goodParamsRequired = ["code", "state"];
const errorParamsRequired = ["error"];
if (as["authorization_response_iss_parameter_supported"]) {
goodParamsRequired.push("iss");
}
let oauthSuccess = urlParams != null;
let oauthError = false;
if (urlParams != null) {
goodParamsRequired.forEach((p) => {
oauthSuccess &&= urlParams.get(p) != null;
});
errorParamsRequired.forEach((p) => {
oauthError ||= urlParams.get(p) != null;
});
}
if (oauthError) {
const error = {
error: _optionalChain([urlParams, 'optionalAccess', _8 => _8.get, 'call', _9 => _9("error")]),
error_description: _optionalChain([urlParams, 'optionalAccess', _10 => _10.get, 'call', _11 => _11("error_description")]),
error_uri: _optionalChain([urlParams, 'optionalAccess', _12 => _12.get, 'call', _13 => _13("error_uri")])
};
log5.error("OpenID Connect error", error);
return error;
}
if (!oauthSuccess) {
const ts = localStorage.getItem("core.auth.oidc");
localStorage.removeItem("core.auth.oidc");
if (ts) {
const exp_at = parseInt(ts);
const now = Date.now();
if (!isNaN(exp_at) && now < exp_at * 1e3) {
localStorage.setItem("core.auth.silentLogin", "1");
this.oidcLogin(idx, true);
}
}
return null;
}
const client = {
client_id: config.client_id,
token_endpoint_auth_method: "none"
};
const state = urlParams.get("state");
let authResponse;
try {
authResponse = oidc.validateAuthResponse(as, client, urlParams, state);
} catch (e) {
if (e instanceof _oauth4webapi.AuthorizationResponseError) {
log5.error("OpenID Authorization error", e);
return { error: e.message };
}
}
log5.info("Getting localStore data, because we have params", urlParams);
const loginDataString = localStorage.getItem("hawtio-oidc-login");
localStorage.removeItem("hawtio-oidc-login");
if (!loginDataString) {
log5.warn("No local data, can't proceed with OpenID authorization grant");
return null;
}
const loginData = JSON.parse(loginDataString);
if (!loginData.cv || !loginData.st) {
log5.warn("Missing local data, can't proceed with OpenID authorization grant");
return null;
}
const res = await oidc.authorizationCodeGrantRequest(as, client, clientAuth, authResponse, config.redirect_uri, loginData.cv, {
[oidc.allowInsecureRequests]: true
}).catch((e) => {
log5.warn("Problem accessing OpenID token endpoint", e);
return null;
});
if (!res) {
const msg = "No authorization code grant response available";
log5.warn(msg);
return { error: msg };
}
const tokenResponse = await oidc.processAuthorizationCodeResponse(as, client, res, {
expectedNonce: loginData.n,
maxAge: oidc.skipAuthTimeCheck
}).catch((e) => {
log5.error("Problem processing OpenID token response", e);
return null;
});
if (!tokenResponse) {
return { error: "Problem processing OpenID token response" };
}
const access_token = tokenResponse["access_token"];
const refresh_token = tokenResponse["refresh_token"];
const id_token = tokenResponse["id_token"];
let at_exp = 0;
try {
const access_token_decoded = _jwtdecode.jwtDecode.call(void 0, access_token);
if (access_token_decoded["exp"]) {
at_exp = access_token_decoded["exp"];
} else {
at_exp = 0;
log5.warn(`Access token doesn't contain "exp" information`);
}
} catch (e) {
log5.warn("Problem determining access token validity", e);
}
const claims = oidc.getValidatedIdTokenClaims(tokenResponse);
if (!claims) {
const msg = "No ID token returned";
log5.warn(msg);
return { error: msg };
}
const user = _nullishCoalesce(_nullishCoalesce(claims.name, () => ( claims.preferred_username)), () => ( claims.sub));
window.history.replaceState(null, "", loginData.h);
localStorage.setItem("core.auth.oidc", `${at_exp}`);
this.setupFetch();
return {
user,
access_token,
refresh_token,
id_token,
at_exp
};
}
__init5() {this.oidcLoginFunction = (oidcIndex) => {
return () => {
return this.oidcLogin(oidcIndex, false);
};
}}
/**
* This is a method made available to `<HawtioLogin>` UI when user clicks "Login with OIDC" button.
*
* @param idx index of the OIDC configuration used
* @param silent whether to start _silent_ (`prompt=none`) Authorization Flow
* @return `false` (a promise resolving to `false`) if the login process can't proceed - login screen should
* display generic error
*/
__init6() {this.oidcLogin = async (idx, silent = false) => {
const cfg = await this.config;
if (!cfg || cfg.length == 0) {
return 1 /* configuration_error */;
}
if (idx >= cfg.length) {
throw new Error(`Invalid OIDC authentication configuration with idx=${idx}`);
}
const config = cfg[idx];
if (!config) {
return 1 /* configuration_error */;
}
if (idx >= this.oidcMetadata.length) {
throw new Error(`Invalid OIDC authentication configuration with idx=${idx}`);
}
this.selectedProvider = idx;
let as = await this.oidcMetadata[idx];
if (!as) {
this.oidcMetadata[idx] = this.fetchOidcMetadata(config).then((md) => {
return this.processOidcMetadata(config, md, idx, false);
});
as = await this.oidcMetadata[idx];
if (!as) {
return 1 /* configuration_error */;
}
}
const available = await this.checkAvailability(idx);
if (!available) {
return 2 /* connect_error */;
}
const code_challenge_method = config.code_challenge_method;
const code_verifier = oidc.generateRandomCodeVerifier();
if (!window.isSecureContext) {
log5.error("Can't perform OpenID Connect authentication in non-secure context");
return 3 /* security_context_error */;
}
const code_challenge = await oidc.calculatePKCECodeChallenge(code_verifier);
const state = oidc.generateRandomState();
const nonce = oidc.generateRandomNonce();
const verifyData = JSON.stringify({ st: state, cv: code_verifier, n: nonce, h: window.location.href });
localStorage.setItem("hawtio-oidc-login", verifyData);
localStorage.setItem("hawtio-oidc-login-idx", JSON.stringify(idx));
log5.info("Added to local storage", verifyData);
const authorizationUrl = new URL(as.authorization_endpoint);
authorizationUrl.searchParams.set("response_type", "code");
authorizationUrl.searchParams.set("response_mode", config.response_mode);
authorizationUrl.searchParams.set("client_id", config.client_id);
authorizationUrl.searchParams.set("redirect_uri", config.redirect_uri);
const basePath = _chunkBHIEXRGKjs.hawtio.getBasePath();
const u = new URL(window.location.href);
u.hash = "";
let redirect = u.pathname;
if (basePath && redirect.startsWith(basePath)) {
redirect = redirect.slice(basePath.length);
if (redirect.startsWith("/")) {
redirect = redirect.slice(1);
}
}
authorizationUrl.searchParams.set("scope", config.scope);
if (code_challenge_method) {
authorizationUrl.searchParams.set("code_challenge_method", code_challenge_method);
authorizationUrl.searchParams.set("code_challenge", code_challenge);
}
authorizationUrl.searchParams.set("state", state);
authorizationUrl.searchParams.set("nonce", nonce);
if (silent) {
authorizationUrl.searchParams.set("prompt", "none");
}
log5.info("Redirecting to ", authorizationUrl);
window.location.replace(authorizationUrl);
return new Promise((_resolve, _reject) => {
log5.debug("Waiting for redirect");
});
}}
/**
* Attempt to communicate with IdP before login/logout without redirection. Actual login/logout
* will redirect (or fail with blank browser page), so it'll be too late to handle situation where IdP is
* simply not responding.
* @param idx
* @private
*/
async checkAvailability(idx) {
try {
const cfg = (await this.config)[idx];
if (cfg) {
let provider = cfg.provider;
if (provider && !provider.toString().endsWith("/")) provider = provider + "/";
const url = new URL(".well-known/openid-configuration", provider);
return this.originalFetch.bind(window)(url).then((_r) => true).catch((_e) => {
return false;
});
}
return false;
} catch (e2) {
return false;
}
}
/**
* Check whether the expiration date from access token means the token is expiring in less than 5 seconds.
* @param at_exp
* @private
*/
isTokenExpiring(at_exp) {
const now = Math.floor(Date.now() / 1e3);
return at_exp - 5 < now;
}
/**
* Integrates this OidcService with UserService for user management.
* @param helpRegistration
*/
registerUserHooks(helpRegistration) {
const fetchUser = async (resolveUser, proceed) => {
if (proceed && !proceed()) {
return { isIgnore: true, isError: false, loginMethod: AUTH_METHOD2 };
}
const userInfo = await this.userInfo;
if (!userInfo) {
return { isIgnore: true, isError: false, loginMethod: AUTH_METHOD2 };
}
localStorage.removeItem("core.auth.silentLogin");
if (!userInfo.error) {
resolveUser({ username: userInfo.user, isLogin: true, loginMethod: AUTH_METHOD2 });
} else {
const errorMessage = '"oidc" plugin error: ' + (_nullishCoalesce(userInfo.error_description, () => ( userInfo.error)));
return { isIgnore: false, isError: true, errorMessage, loginMethod: AUTH_METHOD2 };
}
_chunkBHIEXRGKjs.userService.setToken(userInfo.access_token);
helpRegistration();
return { isIgnore: false, isError: false, loginMethod: AUTH_METHOD2 };
};
_chunkBHIEXRGKjs.userService.addFetchUserHook(AUTH_METHOD2, fetchUser);
const logout = async () => {
if (this.selectedProvider == -1) {
return false;
}
const md = await this.oidcMetadata[this.selectedProvider];
if (_optionalChain([md, 'optionalAccess', _14 => _14.end_session_endpoint])) {
const available = await this.checkAvailability(this.selectedProvider);
if (!available) {
return false;
}
localStorage.removeItem("core.auth.oidc");
const c = (await this.config)[this.selectedProvider];
const userInfo = await this.userInfo;
if (userInfo && userInfo.id_token) {
window.location.assign(
`${_optionalChain([md, 'optionalAccess', _15 => _15.end_session_endpoint])}?post_logout_redirect_uri=${document.baseURI}&client_id=${c.client_id}&id_token_hint=${userInfo.id_token}`
);
} else {
window.location.assign(
`${_optionalChain([md, 'optionalAccess', _16 => _16.end_session_endpoint])}?post_logout_redirect_uri=${document.baseURI}&client_id=${c.client_id}`
);
}
return true;
}
return false;
};
_chunkBHIEXRGKjs.userService.addLogoutHook(AUTH_METHOD2, logout);
}
async updateToken(success, failure) {
const userInfo = await this.userInfo;
if (!userInfo || this.selectedProvider == -1) {
return;
}
if (userInfo.refresh_token) {
const config = (await this.config)[this.selectedProvider];
const enabled = await this.enabled;
const as = await this.oidcMetadata[this.selectedProvider];
if (!config || !enabled || !as) {
return;
}
const client = {
client_id: config.client_id,
token_endpoint_auth_method: "none"
};
const options = {
[oidc.customFetch]: this.originalFetch,
[oidc.allowInsecureRequests]: true
};
const res = await oidc.refreshTokenGrantRequest(as, client, clientAuth, userInfo.refresh_token, options).catch((e) => {
log5.error("Problem refreshing token", e);
if (failure) {
failure();
}
});
if (!res) {
return;
}
const refreshResponse = await oidc.processRefreshTokenResponse(as, client, res).catch((e) => {
log5.error("Problem processing refresh token response", e);
});
if (!refreshResponse) {
return;
}
userInfo.access_token = refreshResponse["access_token"];
userInfo.refresh_token = refreshResponse["refresh_token"];
const access_token_decoded = _jwtdecode.jwtDecode.call(void 0, userInfo.access_token);
if (access_token_decoded["exp"]) {
userInfo.at_exp = access_token_decoded["exp"];
} else {
userInfo.at_exp = 0;
log5.warn(`Access token doesn't contain "exp" information`);
}
this.userInfo = Promise.resolve(userInfo);
success(userInfo);
} else {
log5.error("No refresh token available");
}
}
/**
* Replace global `fetch` function with a delegated call that handles authorization for remote Jolokia agents
* and target agent that may run as proxy (to remote Jolokia agent).
* `fetch` wrapper will also refresh access token if needed.
* @private
*/
async setupFetch() {
let userInfo = await this.userInfo;
if (!userInfo || userInfo.error || userInfo.error_description) {
return;
}
window.fetch = async (input, init) => {
const logPrefix = "Fetch -";
if (userInfo && (!userInfo.access_token || this.isTokenExpiring(userInfo.at_exp))) {
log5.debug(logPrefix, "Refreshing access token");
return new Promise((resolve, reject) => {
this.updateToken(
(_userInfo) => {
if (_userInfo) {
userInfo = _userInfo;
log5.debug(logPrefix, "OIDC token refreshed. Set new value to userService");
_chunkBHIEXRGKjs.userService.setToken(userInfo.access_token);
}
log5.debug(logPrefix, "Re-sending request after successfully updating OIDC token:", input);
resolve(fetch(input, init));
},
() => {
log5.debug(logPrefix, "Logging out due to token update failed");
_chunkBHIEXRGKjs.userService.logout();
reject();
}
);
});
}
init = { ...init };
init.headers = {
...init.headers,
Authorization: `Bearer ${userInfo.access_token}`
};
const token = _chunkBJ6TSPQKjs.getCookie.call(void 0, "XSRF-TOKEN");
if (token) {
init.headers = {
...init.headers,
"X-XSRF-TOKEN": token
};
}
return this.originalFetch(input, init);
};
}
}, _class3);
var oidcService = new OidcService();
// src/plugins/auth/oidc/index.ts
var oidc2 = () => {
let helpRegistered = false;
oidcService.registerUserHooks(() => {
if (!helpRegistered) {
helpRegistry.add("oidc", "OpenID Connect", help_default2, 22);
helpRegistered = true;
}
});
};
// src/preferences/registry.ts
var PreferencesRegistry = (_class4 = class {constructor() { _class4.prototype.__init7.call(this); }
__init7() {this.preferences = {}}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
add(id, title, component, order9 = 100) {
if (this.preferences[id]) {
throw new Error(`Preferences '${id}' already registered`);
}
this.preferences[id] = { id, title, component, order: order9 };
}
getPreferences() {
return Object.values(this.preferences).sort((a, b) => a.order - b.order);
}
reset() {
this.preferences = {};
}
}, _class4);
var preferencesRegistry = new PreferencesRegistry();
// src/util/xml.ts
function parseXML(xml) {
if (!window.DOMParser) throw new Error("Cannot parse xml due to no available native parser");
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
const errorNode = doc.querySelector("parsererror");
if (errorNode) {
throw new Error(`Failed to parse XML: ${errorNode.textContent}`);
}
return doc;
}
function xmlText(element) {
const txt = _optionalChain([element, 'access', _17 => _17.firstChild, 'optionalAccess', _18 => _18.textContent]);
return !txt ? null : txt;
}
function childText(element, childTag) {
const childEl = element.querySelector(childTag);
if (!childEl) return null;
return xmlText(childEl);
}
// src/plugins/camel/globals.ts
var jmxDomain = "org.apache.camel";
var pluginPath2 = "/camel";
var pluginName4 = "hawtio-camel";
var log6 = _chunkBHIEXRGKjs.Logger.get(pluginName4);
var camelContexts = "Camel Contexts";
var domainNodeType = "Camel Domain";
var contextsType = "contexts";
var routesType = "routes";
var routeGroupsType = "routeGroups";
var endpointsType = "endpoints";
var componentsType = "components";
var dataformatsType = "dataformats";
var contextNodeType = "context";
var routeNodeType = "routeNode";
var routeXmlNodeType = "routeXmlNode";
var endpointNodeType = "endpointNode";
var componentNodeType = "componentNode";
var defaultRouteGroupsType = "default";
var mbeansType = "MBeans";
var xmlNodeLocalName = "xmlNodeLocalName";
// src/plugins/camel/endpoints/endpoints-service.ts
var ENDPOINT_OPERATIONS = {
createEndpoint: "createEndpoint(java.lang.String)",
componentNames: "componentNames()",
canSendToEndpoint: "canSendToEndpoint(java.lang.String)",
sendBodyAndHeaders: "sendBodyAndHeaders(java.lang.String, java.lang.Object, java.util.Map)",
sendStringBody: "sendStringBody(java.lang.String, java.lang.String)",
browseAllMessagesAsXml: "browseAllMessagesAsXml(java.lang.Boolean)",
browseRangeMessagesAsXml: "browseRangeMessagesAsXml(java.lang.Integer,java.lang.Integer, java.lang.Boolean)",
endpointStatistics: "endpointStatistics()"
};
async function getEndpoints(node) {
const ctxNode = findContext(node);
if (!ctxNode || ctxNode.childCount() === 0) return [];
const endpointsNode = ctxNode.get(endpointsType, true);
if (!endpointsNode) return [];
const endpoints = [];
for (const ep of endpointsNode.getChildren()) {
if (!ep.objectName) continue;
const attributes = await _chunkBJ6TSPQKjs.jolokiaService.readAttributes(ep.objectName).catch((e) => {
_chunkBHIEXRGKjs.eventService.notify({ type: "warning", message: _chunkBJ6TSPQKjs.jolokiaService.errorMessage(e) });
return {};
});
endpoints.push({
uri: attributes.EndpointUri,
state: attributes.State,
mbean: ep.objectName
});
}
return endpoints;
}
function canCreateEndpoints(node) {
const contextNode = findContext(node);
if (!contextNode) {
return false;
}
return contextNode.hasInvokeRights(ENDPOINT_OPERATIONS.createEndpoint);
}
async function componentNames(node) {
const ctxNode = findContext(node);
if (!ctxNode || ctxNode.childCount() === 0 || !ctxNode.objectName) return [];
const names = await _chunkBJ6TSPQKjs.jolokiaService.execute(ctxNode.objectName, ENDPOINT_OPERATIONS.componentNames);
return names;
}
function notifyError(msg) {
_chunkBHIEXRGKjs.eventService.notify({
type: "danger",
message: msg
});
}
async function createEndpoint(node, name) {
const ctxNode = findContext(node);
if (!ctxNode) {
notifyError("Could not find the CamelContext!");
return;
}
if (!ctxNode.objectName) {
notifyError("Could not find the CamelContext MBean!");
return;
}
_chunkBJ6TSPQKjs.jolokiaService.execute(ctxNode.objectName, ENDPOINT_OPERATIONS.createEndpoint, [name]).then((value) => {
if (value === true) {
_chunkBJ6TSPQKjs.workspace.refreshTree();
_chunkBHIEXRGKjs.eventService.notify({
type: "success",
message: "Creating endpoint",
duration: 3e3
});
} else {
_chunkBHIEXRGKjs.eventService.notify({
type: "danger",
message: "Invalid URI"
});
}
}).catch((error) => {
error = error.replace("org.apache.camel.ResolveEndpointFailedException : ", "");
_chunkBHIEXRGKjs.eventService.notify({
type: "danger",
message: error
});
});
}
function createEndpointFromData(node, componentName, endPointPath, parameters) {
if (!componentName) log6.error("createEndpointFromData: component name must be defined");
if (!endPointPath) log6.error("createEndpointFromData: endpoint path must be defined");
log6.debug("Have endpoint data " + JSON.stringify(parameters));
const uri = componentName + "://" + endPointPath + (parameters ? "?" + Object.entries(parameters).map((entry) => entry.join("=")).join("&") : "");
log6.debug("Creating endpoint for uri: " + uri);
createEndpoint(node, uri);
}
async function loadEndpointSchema(node, componentName) {
const ctxNode = findContext(node);
if (!ctxNode) {
_chunkBHIEXRGKjs.eventService.notify({
type: "danger",
message: "Could not find the CamelContext!"
});
return null;
}
if (_chunkBHIEXRGKjs.isBlank.call(void 0, componentName)) return null;
const camelModel = await getCamelModel(ctxNode);
log6.info("Endpoints - Use Camel model version:", camelModel.apacheCamelModelVersion);
return _nullishCoalesce(camelModel.components[componentName], () => ( null));
}
async function doSendMessage(mbean, body, headers, notify) {
const messageHeaders = {};
headers.forEach((header) => {
const key = header.name;
if (key && key !== "") {
messageHeaders[key] = header.value;
}
});
const context = _optionalChain([mbean, 'access', _19 => _19.parent, 'optionalAccess', _20 => _20.getMetadata, 'call', _21 => _21(contextNodeType)]);
const uri = mbean.name;
if (context && uri) {
let ok = true;
const reply = await _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.canSendToEndpoint, [uri]);
if (!reply) {
notify("warning", "Camel does not support sending to this endpoint.");
ok = false;
}
if (ok) {
if (Object.keys(messageHeaders).length > 0) {
_chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendBodyAndHeaders, [uri, body, messageHeaders]).then((ok2) => {
notify("success", `Message and headers were sent to the ${uri} endpoint`);
});
} else {
_chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendStringBody, [uri, body]).then((ok2) => {
notify("success", `Message was sent to the ${uri} endpoint`);
});
}
}
} else {
if (!mbean) {
notify("danger", "Could not find CamelContext MBean!");
} else {
notify("danger", "Failed to determine endpoint name!");
}
log6.debug("Parsed context and endpoint:", context, mbean);
}
}
async function forwardMessagesToEndpoint(mBean, uri, messages, notify) {
const context = _optionalChain([mBean, 'access', _22 => _22.parent, 'optionalAccess', _23 => _23.getMetadata, 'call', _24 => _24(contextNodeType)]);
if (context && uri && messages && messages.length) {
try {
await _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.createEndpoint, [uri]);
} catch (err) {
notify("danger", `Error: ${err}`);
return;
}
let forwarded = 0;
for (const message of messages) {
const body = message.body;
const messageHeaders = {};
if (message.headers.length > 0) {
message.headers.forEach((header) => {
if (header.key && header.key !== "") {
messageHeaders[header.key] = header.value;
}
});
}
try {
await _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendBodyAndHeaders, [uri, body, messageHeaders]);
forwarded++;
} catch (err) {
notify("danger", `Error: ${err}`);
return;
}
}
const m = forwarded > 1 ? "messages" : "message";
notify("success", `Forwarded ${forwarded} ${m} to endpoint ${uri}`);
}
}
async function getMessagesFromTheEndpoint(mbean, from, to) {
let messageData = [];
const context = _optionalChain([mbean, 'access', _25 => _25.parent, 'optionalAccess', _26 => _26.getMetadata, 'call', _27 => _27(contextNodeType)]);
const browseAll = to === -1;
if (context) {
let reply;
if (browseAll) {
reply = await _chunkBJ6TSPQKjs.jolokiaService.execute(_nullishCoalesce(mbean.objectName, () => ( "")), ENDPOINT_OPERATIONS.browseAllMessagesAsXml, [true]);
} else {
reply = await _chunkBJ6TSPQKjs.jolokiaService.execute(_nullishCoalesce(mbean.objectName, () => ( "")), ENDPOINT_OPERATIONS.browseRangeMessagesAsXml, [
from,
to,
true
]);
}
const messagesXml = parseXML(reply);
messageData = parseMessagesFromXml(messagesXml);
}
return messageData;
}
function parseMessagesFromXml(pDoc) {
const messagesData = [];
const messages = pDoc.getElementsByTagName("message");
for (const message of messages) {
const headers = [];
for (const header of message.getElementsByTagName("header")) {
headers.push({
key: _nullishCoalesce(header.getAttribute("key"), () => ( "")),
value: _nullishCoalesce(header.textContent, () => ( "")),
type: _nullishCoalesce(header.getAttribute("type"), () => ( ""))
});
}
messagesData.push({
messageId: _nullishCoalesce(message.getAttribute("exchangeId"), () => ( "")),
body: _nullishCoalesce(_optionalChain([message, 'access', _28 => _28.getElementsByTagName, 'call', _29 => _29("body"), 'access', _30 => _30[0], 'optionalAccess', _31 => _31.textContent]), () => ( "")),
headers
});
}
return messagesData;
}
async function getEndpointStatistics(node) {
let stats = [];
const registry = getDefaultRuntimeEndpointRegistry(node);
if (registry && registry.objectName) {
const res = await _chunkBJ6TSPQKjs.jolokiaService.execute(registry.objectName, ENDPOINT_OPERATIONS.endpointStatistics);
stats = Object.values(res);
} else {
log6.error("Error with the retrieving the registry");
}
return stats;
}
// src/plugins/camel/icons/CamelImageIcon.tsx
var _jsxruntime = require('react/jsx-runtime');
var CamelImageIcon = (props) => {
const { name, svg, size = 16 } = props;
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { src: svg, width: size + "px", height: size + "px", alt: name });
};
// src/plugins/camel/icons/svg/index.ts
var svg_exports = {};
_chunkBHIEXRGKjs.__export.call(void 0, svg_exports, {
IconNames: () => IconNames,
aggregate: () => aggregate24_default,
bean: () => bean24_default,
breakpoint: () => breakpoint_default,
camel: () => camel_default,
camelContext: () => camel_context_icon_default,
camelRoute: () => camel_route_default,
camelRouteFolder: () => camel_route_folder_default,
camelTracing: () => camel_tracing_default,
channel: () => channel24_default,
channeladapter: () => channeladapter24_default,
channelpurger: () => channelpurger24_default,
choice: () => choice24_default,
commandmessage: () => commandmessage24_default,
competingconsumers: () => competingconsumers24_default,
componentFolder: () => component_folder_default,
contentbasedrouter: () => contentbasedrouter24_default,
contentfilter: () => contentfilter24_default,
controlbus: () => controlbus24_default,
convertbodyto: () => convertbodyto24_default,
correlationidentifier: () => correlationidentifier24_default,
customdataformat: () => customdataformat24_default,
datatypechannel: () => datatypechannel24_default,
deadletterchannel: () => deadletterchannel24_default,
detour: () => detour24_default,
distributionaggregate: () => distributionaggregate24_default,
documentmessage: () => documentmessage24_default,
durablesubscription: () => durablesubscription24_default,
dynamicrouter: () => dynamicrouter24_default,
editCamelRoute: () => edit_camel_route_default,
encapsulatedsynchronous: () => encapsulatedsynchronous24_default,
endpoint: () => endpoint24_default,
endpointFile: () => endpointFile24_default,
endpointFolder: () => endpointFolder24_default,
endpointQueue: () => endpointQueue24_default,
endpointRepository: () => endpointRepository24_default,
endpointTimer: () => endpointTimer24_default,
endpointdrools: () => endpointdrools24_default,
endpoints: () => endpoints_default,
endpointsActivemq: () => activemq24_default,
endpointsAtom: () => atom24_default,
endpointsBean: () => bean24_default2,
endpointsCxf: () => cxf24_default,
endpointsCxfrs: () => cxfrs24_default,
endpointsEjb: () => ejb24_default,
endpointsFacebook: () => facebook24_default,
endpointsFile: () => file24_default,
endpointsFolder: () => endpoints_folder_default,
endpointsFtp: () => ftp24_default,
endpointsFtps: () => ftps24_default,
endpointsImap: () => imap24_default,
endpointsImaps: () => imaps24_default,
endpointsJdbc: () => jdbc24_default,
endpointsJms: () => jms24_default,
endpointsLanguage: () => language24_default,
endpointsLinkedin: () => linkedin24_default,
endpointsLog: () => log24_default,
endpointsMqtt: () => mqtt24_default,
endpointsNetty: () => netty24_default,
endpointsNetty4: () => netty424_default,
endpointsNetty4Http: () => netty4_http24_default,
endpointsNettyHttp: () => netty_http2