From 6ca6a5d88e726e7998eb2d4c3cdc6162ef7c82d6 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:38:53 +0000 Subject: [PATCH 1/2] Allow + in email addresses for @kilocode.ai domain - Modified validateMagicLinkSignupEmail to allow + for @kilocode.ai emails - Updated magicLinkSignupEmailSchema to allow + for @kilocode.ai emails - Added tests for the new behavior - Kept restriction in place for all other domains --- src/lib/schemas/email.test.ts | 20 ++++++++++++++++++-- src/lib/schemas/email.ts | 18 +++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/lib/schemas/email.test.ts b/src/lib/schemas/email.test.ts index 4979a76a3e..1180c1d86c 100644 --- a/src/lib/schemas/email.test.ts +++ b/src/lib/schemas/email.test.ts @@ -16,16 +16,27 @@ describe('validateMagicLinkSignupEmail', () => { expect(result).toEqual({ valid: false, error: MAGIC_LINK_EMAIL_ERRORS.LOWERCASE }); }); - it('should reject email with + character', () => { + it('should reject email with + character for non-kilocode domains', () => { const result = validateMagicLinkSignupEmail('user+tag@example.com'); expect(result).toEqual({ valid: false, error: MAGIC_LINK_EMAIL_ERRORS.NO_PLUS }); }); + it('should allow email with + character for @kilocode.ai domain', () => { + const result = validateMagicLinkSignupEmail('user+tag@kilocode.ai'); + expect(result).toEqual({ valid: true, error: null }); + }); + it('should reject email with both uppercase and +', () => { // Uppercase check happens first const result = validateMagicLinkSignupEmail('User+tag@Example.com'); expect(result).toEqual({ valid: false, error: MAGIC_LINK_EMAIL_ERRORS.LOWERCASE }); }); + + it('should reject uppercase @kilocode.ai email even with +', () => { + // Uppercase check happens first, even for kilocode.ai + const result = validateMagicLinkSignupEmail('User+tag@kilocode.ai'); + expect(result).toEqual({ valid: false, error: MAGIC_LINK_EMAIL_ERRORS.LOWERCASE }); + }); }); describe('magicLinkSignupEmailSchema', () => { @@ -47,11 +58,16 @@ describe('magicLinkSignupEmailSchema', () => { } }); - it('should reject email with + character', () => { + it('should reject email with + character for non-kilocode domains', () => { const result = magicLinkSignupEmailSchema.safeParse('user+tag@example.com'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toBe('Email address cannot contain a + character'); } }); + + it('should allow email with + character for @kilocode.ai domain', () => { + const result = magicLinkSignupEmailSchema.safeParse('user+tag@kilocode.ai'); + expect(result.success).toBe(true); + }); }); diff --git a/src/lib/schemas/email.ts b/src/lib/schemas/email.ts index 41441ee485..3faf971337 100644 --- a/src/lib/schemas/email.ts +++ b/src/lib/schemas/email.ts @@ -13,10 +13,22 @@ export const MAGIC_LINK_EMAIL_ERRORS = { NO_PLUS: 'EMAIL-CANNOT-CONTAIN-PLUS', } as const; +/** + * Domain that is allowed to use + in email addresses for internal testing. + */ +const KILOCODE_DOMAIN = '@kilocode.ai'; + +/** + * Checks if an email is from the kilocode.ai domain. + */ +function isKilocodeDomain(email: string): boolean { + return email.toLowerCase().endsWith(KILOCODE_DOMAIN); +} + /** * Validates that an email is suitable for magic link signup: * - Must be lowercase - * - Must not contain a + character + * - Must not contain a + character (except for @kilocode.ai emails) * * This is NOT enforced during sign-in to existing accounts. * Returns error codes that can be displayed via AuthErrorNotification. @@ -28,7 +40,7 @@ export function validateMagicLinkSignupEmail(email: string): { if (email !== email.toLowerCase()) { return { valid: false, error: MAGIC_LINK_EMAIL_ERRORS.LOWERCASE }; } - if (email.includes('+')) { + if (email.includes('+') && !isKilocodeDomain(email)) { return { valid: false, error: MAGIC_LINK_EMAIL_ERRORS.NO_PLUS }; } return { valid: true, error: null }; @@ -39,6 +51,6 @@ export const magicLinkSignupEmailSchema = z .refine(email => email === email.toLowerCase(), { message: 'Email address must be lowercase', }) - .refine(email => !email.includes('+'), { + .refine(email => !email.includes('+') || isKilocodeDomain(email), { message: 'Email address cannot contain a + character', }); From fc0a4f659420c88f5a06d83496affd3ceab2aa36 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:43:49 +0000 Subject: [PATCH 2/2] Fix email domain validation to strictly match @kilocode.ai The previous implementation used endsWith('@kilocode.ai') which incorrectly allowed emails like mark+klaas@henkkilocode.ai to pass validation. Changed isKilocodeDomain() to extract the domain portion after the @ symbol and compare it exactly against '@kilocode.ai'. Added test cases for lookalike domains to prevent regression. --- src/lib/schemas/email.test.ts | 15 +++++++++++++++ src/lib/schemas/email.ts | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib/schemas/email.test.ts b/src/lib/schemas/email.test.ts index 1180c1d86c..d1268a9fd1 100644 --- a/src/lib/schemas/email.test.ts +++ b/src/lib/schemas/email.test.ts @@ -26,6 +26,12 @@ describe('validateMagicLinkSignupEmail', () => { expect(result).toEqual({ valid: true, error: null }); }); + it('should reject email with + character for lookalike domains ending in kilocode.ai', () => { + // @henkkilocode.ai ends with "kilocode.ai" but is not the @kilocode.ai domain + const result = validateMagicLinkSignupEmail('mark+klaas@henkkilocode.ai'); + expect(result).toEqual({ valid: false, error: MAGIC_LINK_EMAIL_ERRORS.NO_PLUS }); + }); + it('should reject email with both uppercase and +', () => { // Uppercase check happens first const result = validateMagicLinkSignupEmail('User+tag@Example.com'); @@ -70,4 +76,13 @@ describe('magicLinkSignupEmailSchema', () => { const result = magicLinkSignupEmailSchema.safeParse('user+tag@kilocode.ai'); expect(result.success).toBe(true); }); + + it('should reject email with + character for lookalike domains ending in kilocode.ai', () => { + // @henkkilocode.ai ends with "kilocode.ai" but is not the @kilocode.ai domain + const result = magicLinkSignupEmailSchema.safeParse('mark+klaas@henkkilocode.ai'); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues[0].message).toBe('Email address cannot contain a + character'); + } + }); }); diff --git a/src/lib/schemas/email.ts b/src/lib/schemas/email.ts index 3faf971337..a6f2dc32f0 100644 --- a/src/lib/schemas/email.ts +++ b/src/lib/schemas/email.ts @@ -20,9 +20,14 @@ const KILOCODE_DOMAIN = '@kilocode.ai'; /** * Checks if an email is from the kilocode.ai domain. + * Uses strict matching to ensure the domain is exactly @kilocode.ai, + * not a subdomain or lookalike (e.g., @henkkilocode.ai). */ function isKilocodeDomain(email: string): boolean { - return email.toLowerCase().endsWith(KILOCODE_DOMAIN); + const atIndex = email.lastIndexOf('@'); + if (atIndex === -1) return false; + const domain = email.slice(atIndex).toLowerCase(); + return domain === KILOCODE_DOMAIN; } /**