import { expect, use } from 'chai'; import chaiHttp from 'chai-http'; import chaiAsPromised from 'chai-as-promised'; import { ec as ECDSA } from 'elliptic'; import { hashProperty, KeyType, Property, PropertyDataType, PropertyType, Signature, } from '@0auth/message'; import { authPrivacy, verifyPrivacy } from '@0auth/server'; import { DataType, decryptMessage, DynamicFormInput, encryptMessage, extensionListener, getData, getDecryptedMessage, getSignature, hideProperty, sendToExtension, StorageType, storeData, storeSignature, } from '../src'; function mockStorage() { let storage: { [key: string]: string } = {}; return { setItem: (key: string, value: string) => (storage[key] = value), removeItem: (key: string) => delete storage[key], getItem: (key: string) => storage[key] !== undefined ? storage[key] : null, clear: () => (storage = {}), }; } use(chaiHttp); use(chaiAsPromised); describe('test utils', () => { if (global.localStorage === undefined) { Object.defineProperty(global, 'localStorage', { value: mockStorage(), }); } beforeEach(() => { localStorage.clear(); }); it('test LocalStorage Mock', () => { expect( getData('test', DataType.Key, StorageType.LocalStorage).orUndefined(), ).to.be.equal(undefined); expect( getData('test', DataType.Message, StorageType.LocalStorage).orUndefined(), ).to.be.equal(undefined); storeData('test', 'stored key', DataType.Key, StorageType.LocalStorage); storeData( 'test', 'stored message', DataType.Message, StorageType.LocalStorage, ); expect( getData('test', DataType.Key, StorageType.LocalStorage).get(), ).to.be.equal('stored key'); expect( getData('test', DataType.Message, StorageType.LocalStorage).get(), ).to.be.equal('stored message'); }); it('test Encrypt & Decrypt message', () => { const encryptedMessage = encryptMessage('Message', '1q2w3e4r'); const decryptedMessage = decryptMessage(encryptedMessage, '1q2w3e4r'); expect(decryptedMessage).to.be.equal('Message'); expect( decryptMessage( 'U2FsdGVkX1/tAtcP+fxui5NTWXrmvtO2dV5Z4obDLP4=', '1q2w3e4r', ), ).to.be.equal('Message'); }); it('test Encrypt & Decrypt Long message', () => { const message = "{'properties':[{'key':'name','type':0,'value':'Kim'},{'key':'age','type':0,'value':'17'},{'key':'address','type':0,'value':'Seoul'}],'sign':{'authType':0,'keyType':1,'value':'304402207282d176a100f0d5feb3faa160bd39843253ec98487cc3342905260ab592b85e02201fdf55464c12bbe4b93868a6e84da59e816e918d52cc599678605f61846426c6'}}\n"; const encryptedMessage = encryptMessage(message, '1q2w3e4r'); const decryptedMessage = decryptMessage(encryptedMessage, '1q2w3e4r'); expect(decryptedMessage).to.be.equal(message); }); it('test Decrypted Message', () => { expect( getDecryptedMessage( 'test', 'encryption key', StorageType.LocalStorage, ).orNull(), ).to.be.null; storeData( 'test', encryptMessage('stored message', 'encryption key'), DataType.Message, StorageType.LocalStorage, ); expect( getDecryptedMessage( 'test', 'encryption key', StorageType.LocalStorage, ).get(), ).to.be.equal('stored message'); }); }); describe('test store localStorage', () => { const properties: Property[] = [ { key: 'name', type: PropertyType.Raw, dataType: PropertyDataType.String, value: 'Kim', }, { key: 'age', type: PropertyType.Raw, dataType: PropertyDataType.Number, value: 17, }, { key: 'address', type: PropertyType.Raw, dataType: PropertyDataType.String, value: 'Seoul', }, ]; const ecdsa = new ECDSA('secp256k1'); const secret = ecdsa.keyFromPrivate( '2ef40452ec154cd38efdc8ffa52e7f513f7d2b2a77e028342bde96c369e4f77a', ); const sign = authPrivacy(properties, { key: secret.getPrivate('hex'), type: KeyType.ECDSA, }); if (global.localStorage === undefined) { Object.defineProperty(global, 'localStorage', { value: mockStorage(), }); } beforeEach(() => { localStorage.clear(); }); it('test storing signature in localStorage not using password', () => { expect(getSignature('test', StorageType.LocalStorage)).to.be.null; storeSignature('test', properties, sign, StorageType.LocalStorage); const storageData = getSignature('test', StorageType.LocalStorage); expect(storageData).to.be.not.null; expect(storageData?.properties).to.be.deep.equal(properties); expect(storageData?.sign).to.be.deep.equal(sign); }); it('test storing signature in localStorage using password', () => { expect(getSignature('test', StorageType.LocalStorage, 'abc')).to.be.null; storeSignature('test', properties, sign, StorageType.LocalStorage, 'abc'); const storageData = getSignature('test', StorageType.LocalStorage, 'abc'); expect(storageData).to.be.not.null; expect(storageData?.properties).to.be.deep.equal(properties); expect(storageData?.sign).to.be.deep.equal(sign); }); it('test storing signature in localStorage using different key', () => { expect(getSignature('test1', StorageType.LocalStorage), 'abc').to.be.null; expect(getSignature('test2', StorageType.LocalStorage)).to.be.null; storeSignature('test1', properties, sign, StorageType.LocalStorage, 'abc'); expect(getSignature('test2', StorageType.LocalStorage)).to.be.null; }); it('test get stored signature in localStorage using different key', () => { storeSignature('test1', properties, sign, StorageType.LocalStorage, 'abc'); storeSignature('test2', properties, sign, StorageType.LocalStorage); const storageData1 = getSignature('test1', StorageType.LocalStorage, 'abc'); expect(storageData1).to.be.not.null; expect(storageData1?.properties).to.be.deep.equal(properties); expect(storageData1?.sign).to.be.deep.equal(sign); const storageData2 = getSignature('test2', StorageType.LocalStorage); expect(storageData2).to.be.not.null; expect(storageData2?.properties).to.be.deep.equal(properties); expect(storageData2?.sign).to.be.deep.equal(sign); }); }); describe('test hide property', () => { const properties: Property[] = [ { key: 'name', type: PropertyType.Raw, dataType: PropertyDataType.String, value: 'Kim', }, { key: 'age', type: PropertyType.Raw, dataType: PropertyDataType.Number, value: 17, }, { key: 'address', type: PropertyType.Raw, dataType: PropertyDataType.String, value: 'Seoul', }, ]; it('test hide property value', () => { const partiallyHiddenProperties = hideProperty(properties, [ 'name', 'address', ]); expect(partiallyHiddenProperties[0].type).to.be.equal(PropertyType.Hash); expect(partiallyHiddenProperties[0].key).to.be.equal('name'); expect(partiallyHiddenProperties[0].value).to.be.equal( hashProperty(properties[0]), ); expect(partiallyHiddenProperties[2].type).to.be.equal(PropertyType.Hash); expect(partiallyHiddenProperties[2].key).to.be.equal('address'); expect(partiallyHiddenProperties[2].value).to.be.equal( hashProperty(properties[2]), ); expect(partiallyHiddenProperties[1].type).to.be.equal(PropertyType.Raw); expect(partiallyHiddenProperties[1].key).to.be.equal('age'); expect(partiallyHiddenProperties[1].value).to.be.equal(properties[1].value); }); it('test hidden property verification', () => { const ecdsa = new ECDSA('secp256k1'); const key = ecdsa.keyFromPrivate( '2ef40452ec154cd38efdc8ffa52e7f513f7d2b2a77e028342bde96c369e4f77a', ); const sign = authPrivacy(properties, { key: key.getPrivate('hex'), type: KeyType.ECDSA, }); const publicKey = { key: key.getPublic('hex'), type: KeyType.ECDSA }; const partiallyHiddenProperties = hideProperty(properties, [ 'name', 'address', ]); expect( verifyPrivacy(partiallyHiddenProperties, sign, publicKey), ).to.be.equal(true); }); }); type Listener = (m: MessageEvent) => unknown; class MessageEvent { data: { [key: string]: string } = {}; source: Window = window; constructor( type: string, eventInitDict: { [key: string]: { [key: string]: string } }, ) { if (type === 'message') { this.data = eventInitDict.data; } } } function mockWindow( listenerQueue: Listener[], messageQueue: { [key: string]: string }[], ) { return { addEventListener: (type: string, listener: Listener, option: boolean) => { if (type === 'message') { listenerQueue.push(listener); return option; } }, postMessage: (message: { [key: string]: string }, targetOrigin: string) => { if (targetOrigin !== undefined) { const event = new MessageEvent('message', { data: message }); listenerQueue.forEach((listener) => listener(event)); messageQueue.push(message); } }, }; } describe('test extension code', () => { let listenerQueue: Listener[] = []; let messageQueue: { [key: string]: string }[] = []; if (global.window === undefined) { Object.defineProperty(global, 'window', { value: mockWindow(listenerQueue, messageQueue), }); } afterEach(() => { listenerQueue = []; messageQueue = []; }); it('test listener', () => { extensionListener() .add('FORM', (msg) => msg.form) .add('PROPERTY', (msg) => msg.properties) .add('SIGN', (msg) => msg.sign) .listen(); expect(listenerQueue.length).to.be.equal(1); }); it('test listen by extension message', () => { let response: | Property[] | Signature | DynamicFormInput[] | undefined = undefined; extensionListener() .add('FORM', (msg) => { response = msg.form; }) .add('PROPERTY', (msg) => { response = msg.properties; }) .add('SIGN', (msg) => { response = msg.sign; }) .listen(); window.postMessage( { from: 'content', type: 'FORM', message: { form: [1, 2, 3] } }, '*', ); expect(response).to.be.not.undefined; expect(response).to.be.deep.equal([1, 2, 3]); }); it('test listen by website message', () => { let response: | Property[] | Signature | DynamicFormInput[] | undefined = undefined; extensionListener() .add('FORM', (msg) => { response = msg.form; }) .listen(); sendToExtension('FORM', { form: [ { type: PropertyDataType.String, label: 'name', name: 'Name', }, ], }); expect(response).to.be.undefined; expect(messageQueue[0]).deep.equal({ from: 'page', type: 'FORM', message: { form: [ { label: 'name', name: 'Name', type: 'String', }, ], }, }); }); });