fix(nextcloud): integration ignores trusted certificates (#3893)
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
import { FetchError } from "node-fetch";
|
||||||
|
|
||||||
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
|
import { RequestError } from "../request-error";
|
||||||
|
import type { AnyRequestError } from "../request-error";
|
||||||
|
import type { ResponseError } from "../response-error";
|
||||||
|
import { matchErrorCode } from "./fetch-http-error-handler";
|
||||||
|
import { HttpErrorHandler } from "./http-error-handler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* node-fetch was a defacto standard to use fetch in nodejs.
|
||||||
|
*
|
||||||
|
* It is for example used within the cross-fetch package which is used in tsdav.
|
||||||
|
*/
|
||||||
|
export class NodeFetchHttpErrorHandler extends HttpErrorHandler {
|
||||||
|
constructor(private type = "node-fetch") {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||||
|
if (!(error instanceof FetchError)) return undefined;
|
||||||
|
if (error.code === undefined) return undefined;
|
||||||
|
|
||||||
|
logger.debug(`Received ${this.type} request error`, {
|
||||||
|
code: error.code,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestErrorInput = matchErrorCode(error.code);
|
||||||
|
if (!requestErrorInput) return undefined;
|
||||||
|
|
||||||
|
return new RequestError(requestErrorInput, {
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response errors do not exist for fetch as it does not throw errors for non successful responses.
|
||||||
|
*/
|
||||||
|
handleResponseError(_: unknown): ResponseError | undefined {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@ import { logger } from "@homarr/log";
|
|||||||
|
|
||||||
import type { AnyRequestError } from "../request-error";
|
import type { AnyRequestError } from "../request-error";
|
||||||
import { ResponseError } from "../response-error";
|
import { ResponseError } from "../response-error";
|
||||||
import { FetchHttpErrorHandler } from "./fetch-http-error-handler";
|
|
||||||
import { HttpErrorHandler } from "./http-error-handler";
|
import { HttpErrorHandler } from "./http-error-handler";
|
||||||
|
import { NodeFetchHttpErrorHandler } from "./node-fetch-http-error-handler";
|
||||||
|
|
||||||
export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
||||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||||
return new FetchHttpErrorHandler("tsdav").handleRequestError(error);
|
return new NodeFetchHttpErrorHandler("tsdav").handleRequestError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResponseError(error: unknown): ResponseError | undefined {
|
handleResponseError(error: unknown): ResponseError | undefined {
|
||||||
@@ -16,7 +16,7 @@ export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
|||||||
// https://github.com/natelindev/tsdav/blob/bf33f04b1884694d685ee6f2b43fe9354b12d167/src/account.ts#L86
|
// https://github.com/natelindev/tsdav/blob/bf33f04b1884694d685ee6f2b43fe9354b12d167/src/account.ts#L86
|
||||||
if (error.message !== "Invalid credentials") return undefined;
|
if (error.message !== "Invalid credentials") return undefined;
|
||||||
|
|
||||||
logger.debug("Received Tsdav response error", {
|
logger.debug("Received tsdav response error", {
|
||||||
status: 401,
|
status: 401,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import type { Agent } from "https";
|
||||||
|
import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
|
||||||
import * as ical from "node-ical";
|
import * as ical from "node-ical";
|
||||||
import { DAVClient } from "tsdav";
|
import { DAVClient } from "tsdav";
|
||||||
import type { Dispatcher, RequestInit as UndiciFetchRequestInit } from "undici";
|
|
||||||
|
|
||||||
import { createCertificateAgentAsync } from "@homarr/certificates/server";
|
import { createHttpsAgentAsync } from "@homarr/certificates/server";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||||
@@ -16,7 +17,7 @@ import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
|
|||||||
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
||||||
export class NextcloudIntegration extends Integration implements ICalendarIntegration {
|
export class NextcloudIntegration extends Integration implements ICalendarIntegration {
|
||||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||||
const client = await this.createCalendarClientAsync(input.dispatcher);
|
const client = await this.createCalendarClientAsync(await createHttpsAgentAsync(input.options));
|
||||||
await client.login();
|
await client.login();
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
@@ -80,7 +81,7 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createCalendarClientAsync(dispatcher?: Dispatcher) {
|
private async createCalendarClientAsync(agent?: Agent) {
|
||||||
return new DAVClient({
|
return new DAVClient({
|
||||||
serverUrl: this.integration.url,
|
serverUrl: this.integration.url,
|
||||||
credentials: {
|
credentials: {
|
||||||
@@ -90,9 +91,10 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
|||||||
authMethod: "Basic",
|
authMethod: "Basic",
|
||||||
defaultAccountType: "caldav",
|
defaultAccountType: "caldav",
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
// We can use the undici options as the global fetch is used instead of the polyfilled.
|
// tsdav is using cross-fetch which uses node-fetch for nodejs environments.
|
||||||
dispatcher: dispatcher ?? (await createCertificateAgentAsync()),
|
// There is an agent property that is the same type as the http(s) agents of nodejs
|
||||||
} satisfies UndiciFetchRequestInit as RequestInit,
|
agent: agent ?? (await createHttpsAgentAsync()),
|
||||||
|
} satisfies NodeFetchRequestInit as RequestInit,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user