| |
| const { errorsToString } = require('librechat-data-provider'); |
| const { loginSchema, registerSchema } = require('./validators'); |
|
|
| describe('Zod Schemas', () => { |
| describe('loginSchema', () => { |
| it('should validate a correct login object', () => { |
| const result = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: 'password123', |
| }); |
|
|
| expect(result.success).toBe(true); |
| }); |
|
|
| it('should invalidate an incorrect email', () => { |
| const result = loginSchema.safeParse({ |
| email: 'testexample.com', |
| password: 'password123', |
| }); |
|
|
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should invalidate a short password', () => { |
| const result = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: 'pass', |
| }); |
|
|
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle email with unusual characters', () => { |
| const emails = ['test+alias@example.com', 'test@subdomain.example.co.uk']; |
| emails.forEach((email) => { |
| const result = loginSchema.safeParse({ |
| email, |
| password: 'password123', |
| }); |
| expect(result.success).toBe(true); |
| }); |
| }); |
|
|
| it('should invalidate email without a domain', () => { |
| const result = loginSchema.safeParse({ |
| email: 'test@.com', |
| password: 'password123', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should invalidate password with only spaces', () => { |
| const result = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: ' ', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should invalidate password that is too long', () => { |
| const result = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: 'a'.repeat(129), |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should invalidate empty email or password', () => { |
| const result = loginSchema.safeParse({ |
| email: '', |
| password: '', |
| }); |
| expect(result.success).toBe(false); |
| }); |
| }); |
|
|
| describe('registerSchema', () => { |
| it('should validate a correct register object', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| expect(result.success).toBe(true); |
| }); |
|
|
| it('should allow the username to be omitted', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| expect(result.success).toBe(true); |
| }); |
|
|
| it('should invalidate a short name', () => { |
| const result = registerSchema.safeParse({ |
| name: 'Jo', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle empty username by transforming to null', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: '', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| expect(result.success).toBe(true); |
| expect(result.data.username).toBe(null); |
| }); |
|
|
| it('should handle name with special characters', () => { |
| const names = ['Jöhn Dœ', 'John <Doe>']; |
| names.forEach((name) => { |
| const result = registerSchema.safeParse({ |
| name, |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
| expect(result.success).toBe(true); |
| }); |
| }); |
|
|
| it('should handle username with special characters', () => { |
| const usernames = ['john.doe@', 'john..doe']; |
| usernames.forEach((username) => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username, |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
| expect(result.success).toBe(true); |
| }); |
| }); |
|
|
| it('should invalidate mismatched password and confirm_password', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password124', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle email without a TLD', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@domain', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle email with multiple @ symbols', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@domain@com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle name that is too long', () => { |
| const result = registerSchema.safeParse({ |
| name: 'a'.repeat(81), |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle username that is too long', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'a'.repeat(81), |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle password or confirm_password that is too long', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'a'.repeat(129), |
| confirm_password: 'a'.repeat(129), |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle password or confirm_password that is just spaces', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: ' ', |
| confirm_password: ' ', |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle null values for fields', () => { |
| const result = registerSchema.safeParse({ |
| name: null, |
| username: null, |
| email: null, |
| password: null, |
| confirm_password: null, |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle undefined values for fields', () => { |
| const result = registerSchema.safeParse({ |
| name: undefined, |
| username: undefined, |
| email: undefined, |
| password: undefined, |
| confirm_password: undefined, |
| }); |
| expect(result.success).toBe(false); |
| }); |
|
|
| it('should handle extra fields not defined in the schema', () => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| extraField: "I shouldn't be here", |
| }); |
| expect(result.success).toBe(true); |
| }); |
|
|
| it('should handle username with special characters from various languages', () => { |
| const usernames = [ |
| |
| 'éèäöü', |
|
|
| |
| 'Jöhn.Döe@', |
| 'Jöhn_Ü', |
| 'Jöhnß', |
|
|
| |
| 'Jéan-Piérre', |
| 'Élève', |
| 'Fiançée', |
| 'Mère', |
|
|
| |
| 'Niño', |
| 'Señor', |
| 'Muñoz', |
|
|
| |
| 'João', |
| 'Coração', |
| 'Pão', |
|
|
| |
| 'Pietro', |
| 'Bambino', |
| 'Forlì', |
|
|
| |
| 'Mâncare', |
| 'Școală', |
| 'Țară', |
|
|
| |
| 'Niç', |
| 'Màquina', |
| 'Çap', |
|
|
| |
| 'Fjärran', |
| 'Skål', |
| 'Öland', |
|
|
| |
| 'Blåbær', |
| 'Fjord', |
| 'Årstid', |
|
|
| |
| 'Flød', |
| 'Søster', |
| 'Århus', |
|
|
| |
| 'Þór', |
| 'Ætt', |
| 'Öx', |
|
|
| |
| 'Şehir', |
| 'Çocuk', |
| 'Gözlük', |
|
|
| |
| 'Łódź', |
| 'Część', |
| 'Świat', |
|
|
| |
| 'Čaj', |
| 'Řeka', |
| 'Život', |
|
|
| |
| 'Kočka', |
| 'Ľudia', |
| 'Žaba', |
|
|
| |
| 'Čovjek', |
| 'Šuma', |
| 'Žaba', |
|
|
| |
| 'Tűz', |
| 'Ősz', |
| 'Ünnep', |
|
|
| |
| 'Mäki', |
| 'Yö', |
| 'Äiti', |
|
|
| |
| 'Tänav', |
| 'Öö', |
| 'Ülikool', |
|
|
| |
| 'Ēka', |
| 'Ūdens', |
| 'Čempions', |
|
|
| |
| 'Ūsas', |
| 'Ąžuolas', |
| 'Čia', |
|
|
| |
| 'Maïs', |
| 'Geërfd', |
| 'Coördinatie', |
| ]; |
|
|
| const failingUsernames = usernames.reduce((acc, username) => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username, |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| if (!result.success) { |
| acc.push({ username, error: result.error }); |
| } |
|
|
| return acc; |
| }, []); |
|
|
| if (failingUsernames.length > 0) { |
| console.log('Failing Usernames:', failingUsernames); |
| } |
| expect(failingUsernames).toEqual([]); |
| }); |
|
|
| it('should reject invalid usernames', () => { |
| const invalidUsernames = [ |
| 'john{doe}', |
| 'j', |
| 'a'.repeat(81), |
| "' OR '1'='1'; --", |
| '{$ne: null}', |
| '<script>alert("XSS")</script>', |
| '"><script>alert("XSS")</script>', |
| '"><img src=x onerror=alert("XSS")>', |
| ]; |
|
|
| const passingUsernames = []; |
| const failingUsernames = invalidUsernames.reduce((acc, username) => { |
| const result = registerSchema.safeParse({ |
| name: 'John Doe', |
| username, |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| if (!result.success) { |
| acc.push({ username, error: result.error }); |
| } |
|
|
| if (result.success) { |
| passingUsernames.push({ username }); |
| } |
|
|
| return acc; |
| }, []); |
|
|
| expect(failingUsernames.length).toEqual(invalidUsernames.length); |
| }); |
| }); |
|
|
| describe('errorsToString', () => { |
| it('should convert errors to string', () => { |
| const { error } = registerSchema.safeParse({ |
| name: 'Jo', |
| username: 'john_doe', |
| email: 'john@example.com', |
| password: 'password123', |
| confirm_password: 'password123', |
| }); |
|
|
| const result = errorsToString(error.errors); |
| expect(result).toBe('name: String must contain at least 3 character(s)'); |
| }); |
| }); |
|
|
| describe('MIN_PASSWORD_LENGTH environment variable', () => { |
| |
| |
| const minLength = parseInt(process.env.MIN_PASSWORD_LENGTH, 10) || 8; |
|
|
| it('should respect the configured minimum password length for login', () => { |
| |
| const resultValid = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: 'a'.repeat(minLength), |
| }); |
| expect(resultValid.success).toBe(true); |
|
|
| |
| if (minLength > 1) { |
| const resultInvalid = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: 'a'.repeat(minLength - 1), |
| }); |
| expect(resultInvalid.success).toBe(false); |
| } |
| }); |
|
|
| it('should respect the configured minimum password length for registration', () => { |
| |
| const resultValid = registerSchema.safeParse({ |
| name: 'John Doe', |
| email: 'john@example.com', |
| password: 'a'.repeat(minLength), |
| confirm_password: 'a'.repeat(minLength), |
| }); |
| expect(resultValid.success).toBe(true); |
|
|
| |
| if (minLength > 1) { |
| const resultInvalid = registerSchema.safeParse({ |
| name: 'John Doe', |
| email: 'john@example.com', |
| password: 'a'.repeat(minLength - 1), |
| confirm_password: 'a'.repeat(minLength - 1), |
| }); |
| expect(resultInvalid.success).toBe(false); |
| } |
| }); |
|
|
| it('should handle edge case of very short minimum password length', () => { |
| |
| if (minLength <= 3) { |
| const result = loginSchema.safeParse({ |
| email: 'test@example.com', |
| password: 'abc', |
| }); |
| expect(result.success).toBe(minLength <= 3); |
| } else { |
| |
| expect(true).toBe(true); |
| } |
| }); |
| }); |
| }); |
|
|