fix(onboarding): accept self-hosted addresses

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 11:32:13 +02:00
1 parent 9c5ab62b3a
commit f1ce0fffad
5 files changed
+43 -29

No files matched your search

+13 -13
View File
@@ -6,11 +6,11 @@ import {
onboardingDiscoveredAppSchema,
} from "./onboarding";
describe("onboarding URL validation", () => {
test.each(["javascript:alert(1)", "data:text/html,hello", "file:///etc/passwd"])(
"rejects the %s scheme for integrations",
describe("onboarding address validation", () => {
test.each(["server.local:8989", "0.0.0.0:3000", "sonarr", "/services/sonarr"])(
"accepts the self-hosted address %s",
(url) => {
expect(onboardingCreateIntegrationSchema.safeParse({ name: "Unsafe", url }).success).toBe(false);
expect(onboardingCreateIntegrationSchema.safeParse({ name: "Service", url }).success).toBe(true);
},
);
@@ -27,7 +27,7 @@ describe("onboarding URL validation", () => {
).toBe(true);
});
test("accepts bounded integration metadata and rejects invalid metadata", () => {
test("accepts bounded integration metadata and rejects empty or oversized metadata", () => {
expect(
onboardingCreateIntegrationSchema.safeParse({
name: "Sonarr",
@@ -41,7 +41,7 @@ describe("onboarding URL validation", () => {
onboardingCreateIntegrationSchema.safeParse({
name: "Sonarr",
url: "http://sonarr:8989",
pingUrl: "file:///etc/passwd",
pingUrl: "",
}).success,
).toBe(false);
expect(
@@ -53,18 +53,18 @@ describe("onboarding URL validation", () => {
).toBe(false);
});
test("rejects non-web app and ping URLs", () => {
test("accepts custom app and ping addresses", () => {
expect(
onboardingDiscoveredAppSchema.safeParse({
name: "Unsafe",
href: "javascript:alert(1)",
pingUrl: "file:///etc/passwd",
name: "Database",
href: "postgres://database:5432",
pingUrl: "database.local:5432",
iconUrl: null,
}).success,
).toBe(false);
).toBe(true);
});
test("requires every app submitted to completeSetup to have an HTTP(S) href", () => {
test("requires every app submitted to completeSetup to have a non-empty address", () => {
const input = {
server: { defaultLocale: "en", defaultColorScheme: "dark" },
board: {
@@ -80,7 +80,7 @@ describe("onboarding URL validation", () => {
expect(
onboardingCompleteSetupSchema.safeParse({
...input,
apps: [{ ...input.apps[0], href: "https://status.example.com" }],
apps: [{ ...input.apps[0], href: "status.local:3001" }],
}).success,
).toBe(true);
});
+6 -9
View File
@@ -13,10 +13,7 @@ import { boardNameSchema } from "./board";
import { zodEnumFromArray } from "./enums";
const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
const httpUrlSchema = z.url().refine((value) => {
const protocol = new URL(value).protocol;
return protocol === "http:" || protocol === "https:";
}, "Only HTTP and HTTPS URLs are permitted");
const serviceAddressSchema = z.string().trim().min(1).max(2048);
const uniqueIdsSchema = (maxLength = 255) =>
z
.array(z.string().trim().min(1).max(maxLength))
@@ -26,11 +23,11 @@ const sourceIdSchema = z.string().trim().min(1).max(512);
export const onboardingCreateIntegrationSchema = z.object({
name: z.string().trim().min(1).max(127),
url: httpUrlSchema,
url: serviceAddressSchema,
sourceId: sourceIdSchema.optional(),
iconUrl: z.string().trim().min(1).max(2048).nullable().optional(),
description: z.string().trim().max(512).nullable().optional(),
pingUrl: httpUrlSchema.nullable().optional(),
pingUrl: serviceAddressSchema.nullable().optional(),
});
export const onboardingIntegrationDraftSchema = onboardingCreateIntegrationSchema.extend({
@@ -47,15 +44,15 @@ export const onboardingIntegrationDraftSchema = onboardingCreateIntegrationSchem
export const onboardingDiscoveredAppSchema = z.object({
sourceId: sourceIdSchema.optional(),
name: z.string().trim().min(1).max(127),
href: httpUrlSchema.nullable(),
pingUrl: httpUrlSchema.nullable().optional(),
href: serviceAddressSchema.nullable(),
pingUrl: serviceAddressSchema.nullable().optional(),
iconUrl: z.string().trim().min(1).nullable(),
description: z.string().trim().max(512).nullable().optional(),
});
export const onboardingAppDraftSchema = onboardingDiscoveredAppSchema.extend({
sourceId: sourceIdSchema,
href: httpUrlSchema,
href: serviceAddressSchema,
});
const integrationDraftsSchema = z