From 60f25b08f68ef3c1f79f590ab8744f5a69705c69 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Thu, 28 May 2026 00:06:16 +0800 Subject: [PATCH] feat: add isDeckyVaultEmail domain validation helper (Task 1) --- lib/auth/__tests__/domain-block.test.ts | 51 +++++++++++++++++++++++++ lib/auth/domain-block.ts | 18 +++++++++ 2 files changed, 69 insertions(+) create mode 100644 lib/auth/__tests__/domain-block.test.ts create mode 100644 lib/auth/domain-block.ts diff --git a/lib/auth/__tests__/domain-block.test.ts b/lib/auth/__tests__/domain-block.test.ts new file mode 100644 index 0000000..4e929a6 --- /dev/null +++ b/lib/auth/__tests__/domain-block.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from "vitest" +import { isDeckyVaultEmail, DOMAIN_BLOCK_ERROR } from "../domain-block" + +describe("isDeckyVaultEmail", () => { + it("returns true for a @deckyvault.xyz email (lowercase)", () => { + expect(isDeckyVaultEmail("user@deckyvault.xyz")).toBe(true) + }) + + it("returns true for a @deckyvault.xyz email (mixed case)", () => { + expect(isDeckyVaultEmail("User@DeckyVault.xyz")).toBe(true) + }) + + it("returns true for a @deckyvault.xyz email with plus addressing", () => { + expect(isDeckyVaultEmail("user+tag@deckyvault.xyz")).toBe(true) + }) + + it("returns true for a @deckyvault.xyz email with surrounding whitespace", () => { + expect(isDeckyVaultEmail(" admin@deckyvault.xyz ")).toBe(true) + }) + + it("returns false for a @gmail.com email", () => { + expect(isDeckyVaultEmail("user@gmail.com")).toBe(false) + }) + + it("returns false for a @example.com email", () => { + expect(isDeckyVaultEmail("user@example.com")).toBe(false) + }) + + it("returns false for a subdomain like @mail.deckyvault.xyz", () => { + expect(isDeckyVaultEmail("user@mail.deckyvault.xyz")).toBe(false) + }) + + it("returns false for an empty string", () => { + expect(isDeckyVaultEmail("")).toBe(false) + }) + + it("returns false for a string without @", () => { + expect(isDeckyVaultEmail("deckyvault.xyz")).toBe(false) + }) + + it("returns true for a .DECKYVAULT.XYZ email (uppercase domain)", () => { + expect(isDeckyVaultEmail("admin@DECKYVAULT.XYZ")).toBe(true) + }) +}) + +describe("DOMAIN_BLOCK_ERROR", () => { + it("is a non-empty string", () => { + expect(DOMAIN_BLOCK_ERROR).toBeTruthy() + expect(typeof DOMAIN_BLOCK_ERROR).toBe("string") + }) +}) \ No newline at end of file diff --git a/lib/auth/domain-block.ts b/lib/auth/domain-block.ts new file mode 100644 index 0000000..f7dbe09 --- /dev/null +++ b/lib/auth/domain-block.ts @@ -0,0 +1,18 @@ +export const DOMAIN_BLOCK_ERROR = + "Signing up with a @deckyvault.xyz email is not allowed." + +/** + * Returns true if the email address has a deckyvault.xyz domain. + * Handles case-insensitive comparison, whitespace trimming, and + * plus-notation aliases (user+tag@domain → domain). + * + * Only matches the exact domain "deckyvault.xyz" — NOT subdomains + * like "mail.deckyvault.xyz". + */ +export function isDeckyVaultEmail(email: string): boolean { + const trimmed = email.trim().toLowerCase() + const atIndex = trimmed.lastIndexOf("@") + if (atIndex === -1) return false + const domain = trimmed.slice(atIndex + 1) + return domain === "deckyvault.xyz" +} \ No newline at end of file