feat: Add SSL certificate bypass support for self-hosted Confluence instances (#4219)
* Added bypassSSL parameter to constructor and implemented SSL bypass logic in fetchConfluenceData method * Updated generateChunkSource function to include bypassSSL in the encrypted payload * Updated the request body to include bypassSSL in the JSON payload sent to the backend * Updated form submission to include bypassSSL parameter from the checkbox * Added bypass_ssl: "Bypass SSL Certificate Validation" translation * passed these parameters to fetchconfluencepage function for proper resync functionality * allow ignore of SSL cert for Confluence * add translations --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
parent
90e474abcb
commit
3ecf218eea
@ -62,6 +62,8 @@ async function resyncConfluence({ chunkSource }, response) {
|
||||
spaceKey: source.searchParams.get('spaceKey'),
|
||||
accessToken: source.searchParams.get('token'),
|
||||
username: source.searchParams.get('username'),
|
||||
cloud: source.searchParams.get('cloud') === 'true',
|
||||
bypassSSL: source.searchParams.get('bypassSSL') === 'true',
|
||||
});
|
||||
|
||||
if (!success) throw new Error(`Failed to sync Confluence page content. ${reason}`);
|
||||
|
||||
@ -14,6 +14,7 @@ class ConfluencePagesLoader {
|
||||
expand = "body.storage,version",
|
||||
personalAccessToken,
|
||||
cloud = true,
|
||||
bypassSSL = false,
|
||||
}) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.spaceKey = spaceKey;
|
||||
@ -23,6 +24,14 @@ class ConfluencePagesLoader {
|
||||
this.expand = expand;
|
||||
this.personalAccessToken = personalAccessToken;
|
||||
this.cloud = cloud;
|
||||
this.bypassSSL = bypassSSL;
|
||||
this.log("Initialized Confluence Loader");
|
||||
if (this.bypassSSL)
|
||||
this.log("!!SSL bypass is enabled!! Use at your own risk!!");
|
||||
}
|
||||
|
||||
log(message, ...args) {
|
||||
console.log(`\x1b[36m[Confluence Loader]\x1b[0m ${message}`, ...args);
|
||||
}
|
||||
|
||||
get authorizationHeader() {
|
||||
@ -45,7 +54,7 @@ class ConfluencePagesLoader {
|
||||
);
|
||||
return pages.map((page) => this.createDocumentFromPage(page));
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
this.log("Error:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -57,12 +66,16 @@ class ConfluencePagesLoader {
|
||||
Accept: "application/json",
|
||||
};
|
||||
const authHeader = this.authorizationHeader;
|
||||
if (authHeader) {
|
||||
initialHeaders.Authorization = authHeader;
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
if (authHeader) initialHeaders.Authorization = authHeader;
|
||||
|
||||
// Configure fetch options with SSL bypass if enabled
|
||||
const fetchOptions = {
|
||||
headers: initialHeaders,
|
||||
});
|
||||
};
|
||||
|
||||
// If SSL bypass is enabled, set the NODE_TLS_REJECT_UNAUTHORIZED environment variable
|
||||
if (this.bypassSSL) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
const response = await fetch(url, fetchOptions);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch ${url} from Confluence: ${response.status}`
|
||||
@ -70,7 +83,10 @@ class ConfluencePagesLoader {
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch ${url} from Confluence: ${error}`);
|
||||
this.log("Error:", error);
|
||||
throw new Error(error.message);
|
||||
} finally {
|
||||
if (this.bypassSSL) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "1";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ async function loadConfluence(
|
||||
accessToken = null,
|
||||
cloud = true,
|
||||
personalAccessToken = null,
|
||||
bypassSSL = false,
|
||||
},
|
||||
response
|
||||
) {
|
||||
@ -54,6 +55,7 @@ async function loadConfluence(
|
||||
accessToken,
|
||||
cloud,
|
||||
personalAccessToken,
|
||||
bypassSSL,
|
||||
});
|
||||
|
||||
const { docs, error } = await loader
|
||||
@ -100,7 +102,15 @@ async function loadConfluence(
|
||||
description: doc.metadata.title,
|
||||
docSource: `${origin} Confluence`,
|
||||
chunkSource: generateChunkSource(
|
||||
{ doc, baseUrl: origin, spaceKey, accessToken, username, cloud },
|
||||
{
|
||||
doc,
|
||||
baseUrl: origin,
|
||||
spaceKey,
|
||||
accessToken,
|
||||
username,
|
||||
cloud,
|
||||
bypassSSL,
|
||||
},
|
||||
response.locals.encryptionWorker
|
||||
),
|
||||
published: new Date().toLocaleString(),
|
||||
@ -144,6 +154,7 @@ async function fetchConfluencePage({
|
||||
username,
|
||||
accessToken,
|
||||
cloud = true,
|
||||
bypassSSL = false,
|
||||
}) {
|
||||
if (!pageUrl || !baseUrl || !spaceKey || !username || !accessToken) {
|
||||
return {
|
||||
@ -177,6 +188,7 @@ async function fetchConfluencePage({
|
||||
username,
|
||||
accessToken,
|
||||
cloud,
|
||||
bypassSSL,
|
||||
});
|
||||
|
||||
const { docs, error } = await loader
|
||||
@ -240,7 +252,7 @@ function validBaseUrl(baseUrl) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateChunkSource(
|
||||
{ doc, baseUrl, spaceKey, accessToken, username, cloud },
|
||||
{ doc, baseUrl, spaceKey, accessToken, username, cloud, bypassSSL },
|
||||
encryptionWorker
|
||||
) {
|
||||
const payload = {
|
||||
@ -249,6 +261,7 @@ function generateChunkSource(
|
||||
token: accessToken,
|
||||
username,
|
||||
cloud,
|
||||
bypassSSL,
|
||||
};
|
||||
return `confluence://${doc.metadata.url}?payload=${encryptionWorker.encrypt(
|
||||
JSON.stringify(payload)
|
||||
|
||||
@ -9,6 +9,7 @@ export default function ConfluenceOptions() {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [accessType, setAccessType] = useState("username");
|
||||
const [isCloud, setIsCloud] = useState(true);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -31,6 +32,7 @@ export default function ConfluenceOptions() {
|
||||
accessToken: form.get("accessToken"),
|
||||
cloud: form.get("isCloud") === "true",
|
||||
personalAccessToken: form.get("personalAccessToken"),
|
||||
bypassSSL: form.get("bypassSSL") === "true",
|
||||
});
|
||||
|
||||
if (!!error) {
|
||||
@ -77,6 +79,7 @@ export default function ConfluenceOptions() {
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
defaultValue="true"
|
||||
onChange={(e) => setIsCloud(e.target.value === "true")}
|
||||
>
|
||||
<option value="true">Atlassian Cloud</option>
|
||||
<option value="false">Self-hosted</option>
|
||||
@ -250,6 +253,31 @@ export default function ConfluenceOptions() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isCloud && (
|
||||
<div className="w-full flex flex-col py-2">
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<div className="flex flex-col pr-10">
|
||||
<div className="flex flex-col gap-y-1 mb-4">
|
||||
<label className="text-white text-sm font-bold flex gap-x-2 items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="bypassSSL"
|
||||
className="mr-2"
|
||||
defaultChecked={false}
|
||||
/>
|
||||
<p className="font-bold text-theme-text-primary">
|
||||
{t("connectors.confluence.bypass_ssl")}
|
||||
</p>
|
||||
</label>
|
||||
<p className="text-xs font-normal text-theme-text-secondary">
|
||||
{t("connectors.confluence.bypass_ssl_explained")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-y-2 w-full pr-10">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@ -553,6 +553,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -579,6 +579,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Din personlige Confluence-adgangstoken.",
|
||||
task_explained:
|
||||
"Når færdig, vil sideindholdet være tilgængeligt for indlejring i arbejdsområder i dokumentvælgeren.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Dokumenter",
|
||||
|
||||
@ -788,6 +788,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Ihr Confluence persönliches Zugriffstoken.",
|
||||
task_explained:
|
||||
"Sobald der Vorgang abgeschlossen ist, ist der Seiteninhalt im Dokumenten-Picker zur Einbettung in Workspaces verfügbar.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Dokumente",
|
||||
|
||||
@ -849,6 +849,9 @@ const TRANSLATIONS = {
|
||||
token_desc: "Access token for authentication",
|
||||
pat_token: "Confluence Personal Access Token",
|
||||
pat_token_explained: "Your Confluence personal access token.",
|
||||
bypass_ssl: "Bypass SSL Certificate Validation",
|
||||
bypass_ssl_explained:
|
||||
"Enable this option to bypass SSL certificate validation for self-hosted confluence instances with self-signed certificate",
|
||||
task_explained:
|
||||
"Once complete, the page content will be available for embedding into workspaces in the document picker.",
|
||||
},
|
||||
|
||||
@ -801,6 +801,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Tu token de acceso personal de Confluence.",
|
||||
task_explained:
|
||||
"Una vez completado, el contenido de la página estará disponible para incrustar en los espacios de trabajo en el selector de documentos.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Documentos",
|
||||
|
||||
@ -750,6 +750,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Sinu isiklik juurdepääsuvõti.",
|
||||
task_explained:
|
||||
"Kui valmis, on lehe sisu dokumentide valijas tööruumidesse põimimiseks saadaval.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Dokumendid",
|
||||
|
||||
@ -545,6 +545,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -553,6 +553,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -758,6 +758,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "אסימון הגישה האישי שלך ב-Confluence.",
|
||||
task_explained:
|
||||
"לאחר השלמה, תוכן העמוד יהיה זמין להטמעה בסביבות עבודה בבורר המסמכים.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "מסמכים",
|
||||
|
||||
@ -551,6 +551,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -571,6 +571,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Confluenceのパーソナルアクセストークンです。",
|
||||
task_explained:
|
||||
"完了後、ページ内容がドキュメントピッカーからワークスペースに埋め込めるようになります。",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "ドキュメント",
|
||||
|
||||
@ -766,6 +766,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Confluence 계정의 개인 액세스 토큰입니다.",
|
||||
task_explained:
|
||||
"가져오기가 완료되면 페이지 내용이 문서 선택기에서 워크스페이스에 임베딩할 수 있도록 제공됩니다.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "문서 관리",
|
||||
|
||||
@ -780,6 +780,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Jūsu Confluence personiskais piekļuves tokens.",
|
||||
task_explained:
|
||||
"Kad tas būs pabeigts, lapas saturs būs pieejams iegulšanai darba vietās dokumentu atlasītājā.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Dokumenti",
|
||||
|
||||
@ -548,6 +548,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -785,6 +785,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Osobisty token dostępu do Confluence.",
|
||||
task_explained:
|
||||
"Po zakończeniu zawartość strony będzie dostępna do osadzenia w obszarach roboczych w selektorze dokumentów.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Dokumenty",
|
||||
|
||||
@ -764,6 +764,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Seu token pessoal de acesso.",
|
||||
task_explained:
|
||||
"Após conclusão, o conteúdo da página estará disponível para vínculo.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Documentos",
|
||||
|
||||
@ -520,6 +520,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Token-ul tău personal de acces Confluence.",
|
||||
task_explained:
|
||||
"Odată complet, conținutul paginii va fi disponibil pentru embedding în spații de lucru în selectorul de documente.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Documente",
|
||||
|
||||
@ -580,6 +580,8 @@ const TRANSLATIONS = {
|
||||
pat_token_explained: "Ваш личный токен доступа для Confluence.",
|
||||
task_explained:
|
||||
"После завершения содержимое страницы будет доступно для внедрения в рабочие пространства через выбор документов.",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "Документы",
|
||||
|
||||
@ -548,6 +548,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -547,6 +547,8 @@ const TRANSLATIONS = {
|
||||
pat_token: null,
|
||||
pat_token_explained: null,
|
||||
task_explained: null,
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: null,
|
||||
|
||||
@ -715,6 +715,8 @@ const TRANSLATIONS = {
|
||||
pat_token: "Confluence 个人访问令牌",
|
||||
pat_token_explained: "您的 Confluence 个人访问令牌。",
|
||||
task_explained: "完成后,页面内容将可用于在文档选择器中嵌入至工作区。",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "文档",
|
||||
|
||||
@ -544,6 +544,8 @@ const TRANSLATIONS = {
|
||||
pat_token: "Confluence 個人存取權杖",
|
||||
pat_token_explained: "您的 Confluence 個人存取權杖。",
|
||||
task_explained: "完成後,頁面內容將可供嵌入到工作區中的檔案選擇器。",
|
||||
bypass_ssl: null,
|
||||
bypass_ssl_explained: null,
|
||||
},
|
||||
manage: {
|
||||
documents: "文件",
|
||||
|
||||
@ -140,6 +140,7 @@ const DataConnector = {
|
||||
accessToken,
|
||||
cloud,
|
||||
personalAccessToken,
|
||||
bypassSSL,
|
||||
}) {
|
||||
return await fetch(`${API_BASE}/ext/confluence`, {
|
||||
method: "POST",
|
||||
@ -151,6 +152,7 @@ const DataConnector = {
|
||||
accessToken,
|
||||
cloud,
|
||||
personalAccessToken,
|
||||
bypassSSL,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user