|
| 1 | +import { bufferToBase64url, base64urlToBuffer } from './crypto'; |
| 2 | +import { describe, it, expect } from 'vitest'; |
| 3 | +import { encryptFilestashConfig, decryptFilestashConfig, generateFilestashSecretKey } from './crypto'; |
| 4 | + |
| 5 | +const secretKey = 'vIbdVeu77i1dtX2l'; |
| 6 | +const encrypted = '6HoBc1zE4iysloguKSWk5op6kAj-j_N7loY4KfvDIltZXW6JyqVxWjvnOX3mWYGyhYIVZHe-4lOWYHLrvoPIx5nQlMQ='; |
| 7 | +const decryptedValue = '{"strategy":"password_only"}'; |
| 8 | + |
| 9 | +describe('Filestash crypto helpers', () => { |
| 10 | + it('decrypts a known value', () => { |
| 11 | + const decrypted = decryptFilestashConfig(secretKey, encrypted); |
| 12 | + expect(decrypted).toBe(decryptedValue); |
| 13 | + }); |
| 14 | + |
| 15 | + it('encrypts and decrypts round-trip', () => { |
| 16 | + const reEncrypted = encryptFilestashConfig(secretKey, decryptedValue); |
| 17 | + const roundTrip = decryptFilestashConfig(secretKey, reEncrypted); |
| 18 | + expect(roundTrip).toBe(decryptedValue); |
| 19 | + }); |
| 20 | + |
| 21 | + it('generates a valid secret key', () => { |
| 22 | + const key = generateFilestashSecretKey(); |
| 23 | + expect(key).toMatch(/^[a-zA-Z0-9]{16}$/); |
| 24 | + }); |
| 25 | + |
| 26 | + it('encrypts to a different value each time', () => { |
| 27 | + const enc1 = encryptFilestashConfig(secretKey, decryptedValue); |
| 28 | + const enc2 = encryptFilestashConfig(secretKey, decryptedValue); |
| 29 | + expect(enc1).not.toBe(enc2); |
| 30 | + // Both should decrypt to the same value |
| 31 | + expect(decryptFilestashConfig(secretKey, enc1)).toBe(decryptedValue); |
| 32 | + expect(decryptFilestashConfig(secretKey, enc2)).toBe(decryptedValue); |
| 33 | + }); |
| 34 | + |
| 35 | + it('produces values compatible with another filestash config', () => { |
| 36 | + // Test with actual values from the config.json file |
| 37 | + const actualSecretKey = 'vIbdVeu77i1dtX2l'; |
| 38 | + const actualEncryptedParams = '6HoBc1zE4iysloguKSWk5op6kAj-j_N7loY4KfvDIltZXW6JyqVxWjvnOX3mWYGyhYIVZHe-4lOWYHLrvoPIx5nQlMQ='; |
| 39 | + const expectedDecrypted = '{"strategy":"password_only"}'; |
| 40 | + |
| 41 | + // First verify we can decrypt the actual filestash value |
| 42 | + const decrypted = decryptFilestashConfig(actualSecretKey, actualEncryptedParams); |
| 43 | + expect(decrypted).toBe(expectedDecrypted); |
| 44 | + |
| 45 | + // Now encrypt the same value and verify it decrypts correctly |
| 46 | + const ourEncrypted = encryptFilestashConfig(actualSecretKey, expectedDecrypted); |
| 47 | + const roundTrip = decryptFilestashConfig(actualSecretKey, ourEncrypted); |
| 48 | + expect(roundTrip).toBe(expectedDecrypted); |
| 49 | + }); |
| 50 | + |
| 51 | + it('encodes and decodes ascii string correctly', () => { |
| 52 | + const input = 'hello world!'; |
| 53 | + const buf = Buffer.from(input, 'utf-8'); |
| 54 | + const encoded = bufferToBase64url(buf); |
| 55 | + const decoded = base64urlToBuffer(encoded); |
| 56 | + expect(decoded.toString('utf-8')).toBe(input); |
| 57 | + }); |
| 58 | + |
| 59 | + it('encodes and decodes binary data correctly', () => { |
| 60 | + const input = Buffer.from([0, 255, 100, 200, 50, 0, 1, 2, 3, 4, 5]); |
| 61 | + const encoded = bufferToBase64url(input); |
| 62 | + const decoded = base64urlToBuffer(encoded); |
| 63 | + expect(decoded.equals(input)).toBe(true); |
| 64 | + }); |
| 65 | + |
| 66 | + it('decodes Go base64.URLEncoding output', () => { |
| 67 | + // This is base64.URLEncoding of 'hello world!' |
| 68 | + const goEncoded = 'aGVsbG8gd29ybGQh'; |
| 69 | + const decoded = base64urlToBuffer(goEncoded); |
| 70 | + expect(decoded.toString('utf-8')).toBe('hello world!'); |
| 71 | + }); |
| 72 | + |
| 73 | + it('handles padding and no padding', () => { |
| 74 | + const padded = 'aGVsbG8='; // 'hello' with padding |
| 75 | + const unpadded = 'aGVsbG8'; // 'hello' without padding |
| 76 | + expect(base64urlToBuffer(padded).toString('utf-8')).toBe('hello'); |
| 77 | + expect(base64urlToBuffer(unpadded).toString('utf-8')).toBe('hello'); |
| 78 | + }); |
| 79 | + |
| 80 | + it('bufferToBase64url output is always padded to a multiple of 4', () => { |
| 81 | + const inputs = [ |
| 82 | + Buffer.from('hello'), |
| 83 | + Buffer.from('hello world!'), |
| 84 | + Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), |
| 85 | + Buffer.from('a'.repeat(15)), |
| 86 | + Buffer.from('a'.repeat(16)), |
| 87 | + Buffer.from('a'.repeat(17)), |
| 88 | + ]; |
| 89 | + for (const buf of inputs) { |
| 90 | + const encoded = bufferToBase64url(buf); |
| 91 | + expect(encoded.length % 4).toBe(0); |
| 92 | + // If not empty, should end with 0-2 '=' |
| 93 | + if (encoded.length > 0) { |
| 94 | + expect(encoded.endsWith('=') || encoded.endsWith('==') || !encoded.includes('=')).toBe(true); |
| 95 | + } |
| 96 | + } |
| 97 | + }); |
| 98 | +}); |
0 commit comments