Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9699255180 |
No files matched your search
@@ -24,12 +24,14 @@ The What's Up Docker integration lets Homarr connect to your [WUD](https://githu
|
|||||||
<AddingIntegration />
|
<AddingIntegration />
|
||||||
|
|
||||||
:::info URL configuration
|
:::info URL configuration
|
||||||
Use the root URL of your WUD instance, for example `https://wud.example.com`. Homarr calls the `/api/app` and `/api/containers` endpoints under that URL automatically.
|
Use the root URL of your WUD instance, for example `https://wud.example.com`. Homarr uses `/api/containers` under that URL for both connection testing and widget data.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Secrets
|
### Secrets
|
||||||
|
|
||||||
WUD does not require authentication by default. If your instance is protected with [Basic Authentication](https://github.com/getwud/wud/blob/main/docs/configuration/authentications/basic/README.md), you can provide a username and password.
|
WUD 9 requires authentication. Enter your WUD username and password in Homarr; the initial admin credentials configured with `WUD_AUTH_ADMIN_USER` and `WUD_AUTH_ADMIN_PASSWORD` work with Basic authentication. Older WUD versions without authentication can leave both fields empty.
|
||||||
|
|
||||||
|
Credentials are sent to the configured URL over HTTP or HTTPS. Use HTTPS outside a trusted internal network.
|
||||||
|
|
||||||
<IntegrationSecrets
|
<IntegrationSecrets
|
||||||
secrets={[
|
secrets={[
|
||||||
@@ -40,7 +42,7 @@ WUD does not require authentication by default. If your instance is protected wi
|
|||||||
{
|
{
|
||||||
credentials: ["username", "password"],
|
credentials: ["username", "password"],
|
||||||
steps: [
|
steps: [
|
||||||
"Open your WUD instance's configuration and configure a WUD_AUTH_BASIC_* user as described in the WUD documentation",
|
"Use your WUD account credentials; older versions can use a configured WUD_AUTH_BASIC_* user",
|
||||||
"Enter that username and password in Homarr — Basic authentication is handled automatically",
|
"Enter that username and password in Homarr — Basic authentication is handled automatically",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ describe("WudIntegration authentication", () => {
|
|||||||
expect(requestInit?.timeout).toBe(10_000);
|
expect(requestInit?.timeout).toBe(10_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("does not send credentials over a non-HTTPS URL", async () => {
|
test("sends configured Basic auth credentials over an HTTP URL", async () => {
|
||||||
mockFetch.mockResolvedValue(
|
mockFetch.mockResolvedValue(
|
||||||
new Response(JSON.stringify(sampleContainersResponse), {
|
new Response(JSON.stringify(sampleContainersResponse), {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -209,7 +209,7 @@ describe("WudIntegration authentication", () => {
|
|||||||
const integration = new WudIntegration({
|
const integration = new WudIntegration({
|
||||||
id: "test-wud-http",
|
id: "test-wud-http",
|
||||||
name: "Test WUD HTTP",
|
name: "Test WUD HTTP",
|
||||||
url: "http://wud.example.com",
|
url: "http://wud:3000",
|
||||||
externalUrl: null,
|
externalUrl: null,
|
||||||
decryptedSecrets: [
|
decryptedSecrets: [
|
||||||
{ kind: "username", value: TEST_USERNAME },
|
{ kind: "username", value: TEST_USERNAME },
|
||||||
@@ -221,7 +221,7 @@ describe("WudIntegration authentication", () => {
|
|||||||
|
|
||||||
const [, requestInit] = mockFetch.mock.calls[0] ?? [];
|
const [, requestInit] = mockFetch.mock.calls[0] ?? [];
|
||||||
const headers = (requestInit?.headers ?? {}) as Record<string, string>;
|
const headers = (requestInit?.headers ?? {}) as Record<string, string>;
|
||||||
expect(headers.Authorization).toBeUndefined();
|
expect(headers.Authorization).toBe(`Basic ${Buffer.from(`${TEST_USERNAME}:${TEST_PASSWORD}`).toString("base64")}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,15 +238,16 @@ describe("WudIntegration testing endpoint", () => {
|
|||||||
fetchAsync: vi.fn().mockResolvedValue(response),
|
fetchAsync: vi.fn().mockResolvedValue(response),
|
||||||
}) as unknown as IntegrationTestingInput;
|
}) as unknown as IntegrationTestingInput;
|
||||||
|
|
||||||
test("returns success when /api/app responds 200", async () => {
|
test("returns success when /api/containers responds 200", async () => {
|
||||||
const input = makeInput(new Response(JSON.stringify({ name: "wud", version: "5.0.0" }), { status: 200 }));
|
const input = makeInput(new Response(JSON.stringify(sampleContainersResponse), { status: 200 }));
|
||||||
|
|
||||||
const result = await invokeTesting(createIntegration(), input);
|
const result = await invokeTesting(createIntegration(), input);
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
expect(String(vi.mocked(input.fetchAsync).mock.calls[0]?.[0])).toBe(`${TEST_URL}/api/containers`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("returns a status-code failure when /api/app responds non-OK", async () => {
|
test("returns a status-code failure when /api/containers responds non-OK", async () => {
|
||||||
const input = makeInput(new Response("unauthorized", { status: 401 }));
|
const input = makeInput(new Response("unauthorized", { status: 401 }));
|
||||||
|
|
||||||
const result = await invokeTesting(createIntegration(), input);
|
const result = await invokeTesting(createIntegration(), input);
|
||||||
@@ -255,7 +256,7 @@ describe("WudIntegration testing endpoint", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("passes the Basic auth header through to fetchAsync when configured", async () => {
|
test("passes the Basic auth header through to fetchAsync when configured", async () => {
|
||||||
const response = new Response(JSON.stringify({ name: "wud", version: "5.0.0" }), { status: 200 });
|
const response = new Response(JSON.stringify(sampleContainersResponse), { status: 200 });
|
||||||
const input = makeInput(response);
|
const input = makeInput(response);
|
||||||
|
|
||||||
await invokeTesting(createIntegrationWithBasicAuth(), input);
|
await invokeTesting(createIntegrationWithBasicAuth(), input);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const CONTAINERS_REQUEST_TIMEOUT_MS = 10_000;
|
|||||||
|
|
||||||
export class WudIntegration extends Integration {
|
export class WudIntegration extends Integration {
|
||||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||||
const response = await input.fetchAsync(this.url("/api/app"), {
|
const response = await input.fetchAsync(this.url("/api/containers"), {
|
||||||
headers: this.getAuthHeaders(),
|
headers: this.getAuthHeaders(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ export class WudIntegration extends Integration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await response.json();
|
await parseWudContainersResponseAsync(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return TestConnectionError.ParseResult(
|
return TestConnectionError.ParseResult(
|
||||||
new ParseError("Invalid WUD app response", {
|
new ParseError("Invalid WUD containers response", {
|
||||||
cause: error instanceof Error ? error : new Error(String(error)),
|
cause: error instanceof Error ? error : new Error(String(error)),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -55,10 +55,6 @@ export class WudIntegration extends Integration {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.url("/api/app").protocol !== "https:") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentials = Buffer.from(`${this.getSecretValue("username")}:${this.getSecretValue("password")}`).toString(
|
const credentials = Buffer.from(`${this.getSecretValue("username")}:${this.getSecretValue("password")}`).toString(
|
||||||
"base64",
|
"base64",
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user