From 3821c276092c89f241b6394cbfbb1c6bd02d4c1f Mon Sep 17 00:00:00 2001 From: Steve Jobs Date: Fri, 29 May 2026 13:59:07 +0200 Subject: [PATCH 1/2] Fix vault domain allowed origins updates --- src/lib/server/services/vault-items.ts | 30 +++++++++++++--- tests/integration/vaults.test.ts | 47 ++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/lib/server/services/vault-items.ts b/src/lib/server/services/vault-items.ts index c673e81..79488d6 100644 --- a/src/lib/server/services/vault-items.ts +++ b/src/lib/server/services/vault-items.ts @@ -8,8 +8,21 @@ function slugify(name: string): string { return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); } +function normalizeDomain(domain: string): string { + const trimmed = domain.trim(); + if (!trimmed) return ""; + + try { + const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`); + return url.host; + } catch { + return trimmed.replace(/^https?:\/\//, "").replace(/\/.*$/, ""); + } +} + function deriveAllowedOrigins(domain: string): string[] { - return [`https://${domain}`, `https://*.${domain}`]; + const normalizedDomain = normalizeDomain(domain); + return [`https://${normalizedDomain}`, `https://*.${normalizedDomain}`]; } type FieldInput = { name: string; value: string; sensitive?: boolean }; @@ -19,12 +32,13 @@ export async function createItem( data: { name: string; domain?: string; description?: string; allowedOrigins?: string[]; fields?: FieldInput[] }, ) { const slug = slugify(data.name); - const allowedOrigins = data.allowedOrigins ?? (data.domain ? deriveAllowedOrigins(data.domain) : null); + const domain = data.domain ? normalizeDomain(data.domain) : null; + const allowedOrigins = data.allowedOrigins ?? (domain ? deriveAllowedOrigins(domain) : null); let item; try { [item] = await db.insert(vaultItems).values({ - vaultId, name: data.name, slug, domain: data.domain ?? null, + vaultId, name: data.name, slug, domain, description: data.description ?? null, allowedOrigins, }).returning(); } catch (err: unknown) { @@ -74,7 +88,15 @@ export async function updateItem( id: string, data: { name?: string; domain?: string | null; description?: string | null; allowedOrigins?: string[] | null }, ) { - const [row] = await db.update(vaultItems).set({ ...data, updatedAt: new Date() }) + const updates = { ...data }; + + if ("domain" in updates) { + const domain = updates.domain ? normalizeDomain(updates.domain) : null; + updates.domain = domain; + updates.allowedOrigins ??= domain ? deriveAllowedOrigins(domain) : null; + } + + const [row] = await db.update(vaultItems).set({ ...updates, updatedAt: new Date() }) .where(eq(vaultItems.id, id)).returning(); return row ?? null; } diff --git a/tests/integration/vaults.test.ts b/tests/integration/vaults.test.ts index 710a871..f3a619a 100644 --- a/tests/integration/vaults.test.ts +++ b/tests/integration/vaults.test.ts @@ -125,6 +125,53 @@ describe("vault items service", () => { expect(item.allowedOrigins).toContain("https://*.ing.nl"); }); + it("normalizes URL domains before deriving allowedOrigins", async () => { + const item = await createItem(vaultId, { + name: "Webgains", + domain: "https://platform.webgains.io/path", + fields: [], + }); + + expect(item.domain).toBe("platform.webgains.io"); + expect(item.allowedOrigins).toEqual([ + "https://platform.webgains.io", + "https://*.platform.webgains.io", + ]); + }); + + it("updates allowedOrigins when domain changes", async () => { + const item = await createItem(vaultId, { name: "Login", domain: "github.com", fields: [] }); + + const updated = await updateItem(item.id, { domain: "https://platform.webgains.io/" }); + + expect(updated?.domain).toBe("platform.webgains.io"); + expect(updated?.allowedOrigins).toEqual([ + "https://platform.webgains.io", + "https://*.platform.webgains.io", + ]); + }); + + it("clears allowedOrigins when domain is cleared", async () => { + const item = await createItem(vaultId, { name: "Login", domain: "github.com", fields: [] }); + + const updated = await updateItem(item.id, { domain: null }); + + expect(updated?.domain).toBeNull(); + expect(updated?.allowedOrigins).toBeNull(); + }); + + it("preserves explicit allowedOrigins when domain changes", async () => { + const item = await createItem(vaultId, { name: "Login", domain: "github.com", fields: [] }); + + const updated = await updateItem(item.id, { + domain: "webgains.io", + allowedOrigins: ["https://login.webgains.io"], + }); + + expect(updated?.domain).toBe("webgains.io"); + expect(updated?.allowedOrigins).toEqual(["https://login.webgains.io"]); + }); + it("deletes item and cascades to fields", async () => { const item = await createItem(vaultId, { name: "To Delete", From 64c4e79d27b299a186c30c331d5972338adf9f83 Mon Sep 17 00:00:00 2001 From: Steve Jobs Date: Fri, 29 May 2026 14:13:34 +0200 Subject: [PATCH 2/2] Set vault encryption key in field value tests --- tests/integration/vault-field-value.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/integration/vault-field-value.test.ts b/tests/integration/vault-field-value.test.ts index 151d0b2..e01a983 100644 --- a/tests/integration/vault-field-value.test.ts +++ b/tests/integration/vault-field-value.test.ts @@ -7,7 +7,10 @@ import { auditLogs } from "$lib/server/db/schema"; import { eq, desc } from "drizzle-orm"; describe("vault field value with origin validation", () => { - beforeEach(async () => { await truncateAll(); }); + beforeEach(async () => { + process.env.VAULT_ENCRYPTION_KEY = Buffer.from("a]3Fq!9Lp@2Xw#7Yz&5Bv*8Cn$4Dm%6E").toString("base64"); + await truncateAll(); + }); it("returns value when origin matches allowedOrigins", async () => { const { token } = await createTestToken();