diff --git a/packages/backend/src/models/__snapshots__/app-auth-client.test.js.snap b/packages/backend/src/models/__snapshots__/app-auth-client.test.js.snap new file mode 100644 index 00000000..87b5cc8c --- /dev/null +++ b/packages/backend/src/models/__snapshots__/app-auth-client.test.js.snap @@ -0,0 +1,39 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AppAuthClient model > jsonSchema should have correct validations 1`] = ` +{ + "properties": { + "active": { + "type": "boolean", + }, + "appKey": { + "type": "string", + }, + "authDefaults": { + "type": [ + "string", + "null", + ], + }, + "createdAt": { + "type": "string", + }, + "formattedAuthDefaults": { + "type": "object", + }, + "id": { + "format": "uuid", + "type": "string", + }, + "updatedAt": { + "type": "string", + }, + }, + "required": [ + "name", + "appKey", + "formattedAuthDefaults", + ], + "type": "object", +} +`; diff --git a/packages/backend/src/models/app-auth-client.js b/packages/backend/src/models/app-auth-client.js index 0121a727..e4e9753e 100644 --- a/packages/backend/src/models/app-auth-client.js +++ b/packages/backend/src/models/app-auth-client.js @@ -31,6 +31,7 @@ class AppAuthClient extends Base { delete this.formattedAuthDefaults; } + decryptData() { if (!this.eligibleForDecryption()) return; diff --git a/packages/backend/src/models/app-auth-client.test.js b/packages/backend/src/models/app-auth-client.test.js new file mode 100644 index 00000000..d95ade3d --- /dev/null +++ b/packages/backend/src/models/app-auth-client.test.js @@ -0,0 +1,179 @@ +import { describe, it, expect, vi } from 'vitest'; +import AES from 'crypto-js/aes.js'; +import enc from 'crypto-js/enc-utf8.js'; + +import AppAuthClient from './app-auth-client.js'; +import appConfig from '../config/app.js'; +import { createAppAuthClient } from '../../test/factories/app-auth-client.js'; + +describe('AppAuthClient model', () => { + it('tableName should return correct name', () => { + expect(AppAuthClient.tableName).toBe('app_auth_clients'); + }); + + it('jsonSchema should have correct validations', () => { + expect(AppAuthClient.jsonSchema).toMatchSnapshot(); + }); + + describe('encryptData', () => { + it('should return undefined if eligibleForEncryption is not true', async () => { + vi.spyOn( + AppAuthClient.prototype, + 'eligibleForEncryption' + ).mockReturnValue(false); + + const appAuthClient = new AppAuthClient(); + + expect(appAuthClient.encryptData()).toBeUndefined(); + }); + + it('should encrypt formattedAuthDefaults and set it to authDefaults', async () => { + vi.spyOn( + AppAuthClient.prototype, + 'eligibleForEncryption' + ).mockReturnValue(true); + + const formattedAuthDefaults = { + key: 'value', + }; + + const appAuthClient = new AppAuthClient(); + appAuthClient.formattedAuthDefaults = formattedAuthDefaults; + appAuthClient.encryptData(); + + const expectedDecryptedValue = JSON.parse( + AES.decrypt( + appAuthClient.authDefaults, + appConfig.encryptionKey + ).toString(enc) + ); + + expect(formattedAuthDefaults).toStrictEqual(expectedDecryptedValue); + expect(appAuthClient.authDefaults).not.toEqual(formattedAuthDefaults); + }); + + it('should encrypt formattedAuthDefaults and remove formattedAuthDefaults', async () => { + vi.spyOn( + AppAuthClient.prototype, + 'eligibleForEncryption' + ).mockReturnValue(true); + + const formattedAuthDefaults = { + key: 'value', + }; + + const appAuthClient = new AppAuthClient(); + appAuthClient.formattedAuthDefaults = formattedAuthDefaults; + appAuthClient.encryptData(); + + expect(appAuthClient.formattedAuthDefaults).not.toBeDefined(); + }); + }); + + describe('decryptData', () => { + it('should return undefined if eligibleForDecryption is not true', () => { + vi.spyOn( + AppAuthClient.prototype, + 'eligibleForDecryption' + ).mockReturnValue(false); + + const appAuthClient = new AppAuthClient(); + + expect(appAuthClient.decryptData()).toBeUndefined(); + }); + + it('should decrypt authDefaults and set it to formattedAuthDefaults', async () => { + vi.spyOn( + AppAuthClient.prototype, + 'eligibleForDecryption' + ).mockReturnValue(true); + + const formattedAuthDefaults = { + key: 'value', + }; + + const authDefaults = AES.encrypt( + JSON.stringify(formattedAuthDefaults), + appConfig.encryptionKey + ).toString(); + + const appAuthClient = new AppAuthClient(); + appAuthClient.authDefaults = authDefaults; + appAuthClient.decryptData(); + + expect(appAuthClient.formattedAuthDefaults).toStrictEqual( + formattedAuthDefaults + ); + expect(appAuthClient.authDefaults).not.toEqual(formattedAuthDefaults); + }); + }); + + describe('eligibleForEncryption', () => { + it('should return true when formattedAuthDefaults property exists', async () => { + const appAuthClient = await createAppAuthClient(); + + expect(appAuthClient.eligibleForEncryption()).toBe(true); + }); + + it("should return false when formattedAuthDefaults property doesn't exist", async () => { + const appAuthClient = await createAppAuthClient(); + + delete appAuthClient.formattedAuthDefaults; + + expect(appAuthClient.eligibleForEncryption()).toBe(false); + }); + }); + + describe('eligibleForDecryption', () => { + it('should return true when authDefaults property exists', async () => { + const appAuthClient = await createAppAuthClient(); + + expect(appAuthClient.eligibleForDecryption()).toBe(true); + }); + + it("should return false when authDefaults property doesn't exist", async () => { + const appAuthClient = await createAppAuthClient(); + + delete appAuthClient.authDefaults; + + expect(appAuthClient.eligibleForDecryption()).toBe(false); + }); + }); + + it('$beforeInsert should call AppAuthClient.encryptData', async () => { + const appAuthClientBeforeInsertSpy = vi.spyOn( + AppAuthClient.prototype, + 'encryptData' + ); + + await createAppAuthClient(); + + expect(appAuthClientBeforeInsertSpy).toHaveBeenCalledOnce(); + }); + + it('$beforeUpdate should call AppAuthClient.encryptData', async () => { + const appAuthClient = await createAppAuthClient(); + + const appAuthClientBeforeUpdateSpy = vi.spyOn( + AppAuthClient.prototype, + 'encryptData' + ); + + await appAuthClient.$query().patchAndFetch({ name: 'sample' }); + + expect(appAuthClientBeforeUpdateSpy).toHaveBeenCalledOnce(); + }); + + it('$afterFind should call AppAuthClient.decryptData', async () => { + const appAuthClient = await createAppAuthClient(); + + const appAuthClientAfterFindSpy = vi.spyOn( + AppAuthClient.prototype, + 'decryptData' + ); + + await appAuthClient.$query(); + + expect(appAuthClientAfterFindSpy).toHaveBeenCalledOnce(); + }); +});