mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-08-17 21:25:52 +02:00
Prevent admins from entering malicious javascript into link settings
This commit is contained in:
@@ -43,6 +43,10 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
Custom changes:
|
||||
* Added derivedHref to prevent malicious js injection via href prop.
|
||||
*/
|
||||
let {
|
||||
class: className,
|
||||
variant = "default",
|
||||
@@ -55,15 +59,29 @@
|
||||
isLoading,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
|
||||
let derivedHref = $derived.by(() => {
|
||||
if (
|
||||
href &&
|
||||
(href.startsWith("http://") ||
|
||||
href.startsWith("https://") ||
|
||||
href.startsWith("mailto:") ||
|
||||
href.startsWith("tel:") ||
|
||||
href.startsWith("/"))
|
||||
) {
|
||||
return href;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
{#if derivedHref}
|
||||
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
href={disabled ? undefined : href}
|
||||
href={disabled ? undefined : derivedHref}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? "link" : undefined}
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "$lib/logger";
|
||||
import { and, count, eq, ne, not, or } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { ConflictError, NotFoundError, ValidationError } from "../utils/errors";
|
||||
import { redactDbUrl } from "../utils/url";
|
||||
import { isLinkValid, redactDbUrl } from "../utils/url";
|
||||
|
||||
if (!process.env.BUILDING && !env.DATABASE_URL) {
|
||||
throw new Error("DATABASE_URL is not set");
|
||||
@@ -223,7 +223,7 @@ export class TenantAdminService {
|
||||
updateData: Partial<
|
||||
Pick<
|
||||
InsertTenant,
|
||||
"longName" | "shortName" | "descriptions" | "languages" | "logo" | "domain"
|
||||
"longName" | "shortName" | "descriptions" | "languages" | "logo" | "domain" | "links"
|
||||
>
|
||||
>,
|
||||
) {
|
||||
@@ -237,6 +237,15 @@ export class TenantAdminService {
|
||||
throw new ValidationError("Shortname cannot be changed");
|
||||
}
|
||||
|
||||
// Check each link
|
||||
if (updateData.links) {
|
||||
for (const link of Object.values(updateData.links)) {
|
||||
if (!isLinkValid(link)) {
|
||||
throw new ValidationError("Links must start with http or https or be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if domain is already in use
|
||||
if (updateData.domain) {
|
||||
const domainExists = await centralDb
|
||||
|
||||
@@ -4,3 +4,9 @@ export const redactDbUrl = (input: string) => {
|
||||
url.password = "redacted-pw";
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
export const isLinkValid = (link: string | undefined) => {
|
||||
return (
|
||||
link?.startsWith("http://") || link?.startsWith("https://") || link === "" || link === undefined
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,16 @@ import { m } from "$i18n/messages";
|
||||
import { z } from "zod";
|
||||
|
||||
const optionalUrl = (errorMessage: string) =>
|
||||
z.union([z.literal(""), z.url({ message: errorMessage })]).optional();
|
||||
z
|
||||
.union([
|
||||
z.literal(""),
|
||||
z
|
||||
.url({ message: errorMessage })
|
||||
.refine((url) => url.startsWith("http://") || url.startsWith("https://"), {
|
||||
message: errorMessage,
|
||||
}),
|
||||
])
|
||||
.optional();
|
||||
|
||||
export const formSchema = z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user