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:
Neha Prasad 2025-11-26 04:02:10 +05:30 committed by GitHub
parent 90e474abcb
commit 3ecf218eea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 115 additions and 9 deletions

View File

@ -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}`);

View File

@ -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";
}
}

View File

@ -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)

View File

@ -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"

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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.",
},

View File

@ -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",

View File

@ -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",

View File

@ -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,

View File

@ -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,

View File

@ -758,6 +758,8 @@ const TRANSLATIONS = {
pat_token_explained: "אסימון הגישה האישי שלך ב-Confluence.",
task_explained:
"לאחר השלמה, תוכן העמוד יהיה זמין להטמעה בסביבות עבודה בבורר המסמכים.",
bypass_ssl: null,
bypass_ssl_explained: null,
},
manage: {
documents: "מסמכים",

View File

@ -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,

View File

@ -571,6 +571,8 @@ const TRANSLATIONS = {
pat_token_explained: "Confluenceのパーソナルアクセストークンです。",
task_explained:
"完了後、ページ内容がドキュメントピッカーからワークスペースに埋め込めるようになります。",
bypass_ssl: null,
bypass_ssl_explained: null,
},
manage: {
documents: "ドキュメント",

View File

@ -766,6 +766,8 @@ const TRANSLATIONS = {
pat_token_explained: "Confluence 계정의 개인 액세스 토큰입니다.",
task_explained:
"가져오기가 완료되면 페이지 내용이 문서 선택기에서 워크스페이스에 임베딩할 수 있도록 제공됩니다.",
bypass_ssl: null,
bypass_ssl_explained: null,
},
manage: {
documents: "문서 관리",

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -580,6 +580,8 @@ const TRANSLATIONS = {
pat_token_explained: "Ваш личный токен доступа для Confluence.",
task_explained:
"После завершения содержимое страницы будет доступно для внедрения в рабочие пространства через выбор документов.",
bypass_ssl: null,
bypass_ssl_explained: null,
},
manage: {
documents: "Документы",

View File

@ -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,

View File

@ -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,

View File

@ -715,6 +715,8 @@ const TRANSLATIONS = {
pat_token: "Confluence 个人访问令牌",
pat_token_explained: "您的 Confluence 个人访问令牌。",
task_explained: "完成后,页面内容将可用于在文档选择器中嵌入至工作区。",
bypass_ssl: null,
bypass_ssl_explained: null,
},
manage: {
documents: "文档",

View File

@ -544,6 +544,8 @@ const TRANSLATIONS = {
pat_token: "Confluence 個人存取權杖",
pat_token_explained: "您的 Confluence 個人存取權杖。",
task_explained: "完成後,頁面內容將可供嵌入到工作區中的檔案選擇器。",
bypass_ssl: null,
bypass_ssl_explained: null,
},
manage: {
documents: "文件",

View File

@ -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())