From 84794deb7f25ebd2d05d1cfe7f8c87599a98f949 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Fri, 10 Jan 2025 18:22:50 +0100 Subject: [PATCH 1/9] switch last two encryption functions --- .../Members/containers/InviteDialogContainer.tsx | 10 ++++++---- src/app/store/slices/sharedLinks/index.ts | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx index 25f8c936f..fec88534e 100644 --- a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx @@ -6,7 +6,7 @@ import errorService from '../../../../../core/services/error.service'; import navigationService from '../../../../../core/services/navigation.service'; import workspacesService from '../../../../../core/services/workspace.service'; import { AppView } from '../../../../../core/types'; -import { encryptMessageWithPublicKey } from '../../../../../crypto/services/pgp.service'; +import { hybridEncryptMessageWithPublicKey } from '../../../../../crypto/services/pgp.service'; import notificationsService, { ToastType } from '../../../../../notifications/services/notifications.service'; import { RootState } from '../../../../../store'; import UserInviteDialog from '../InviteDialog'; @@ -40,10 +40,11 @@ const processInvitation = async ( return; } const { mnemonic } = user; - let publicKey; + let publicKey, publicKyberKey; try { const publicKeyResponse = await userService.getPublicKeyByEmail(email); publicKey = publicKeyResponse.publicKey; + publicKyberKey = publicKeyResponse.publicKyberKey; } catch (err) { console.log(err); } @@ -52,14 +53,15 @@ const processInvitation = async ( if (isNewUser) { const preCreatedUserResponse = await userService.preCreateUser(email); publicKey = preCreatedUserResponse.publicKey; + publicKyberKey = preCreatedUserResponse.publicKyberKey; } - const encryptedMnemonic = await encryptMessageWithPublicKey({ + const encryptedMnemonicInBase64 = await hybridEncryptMessageWithPublicKey({ message: mnemonic, publicKeyInBase64: publicKey, + publicKyberKeyBase64: publicKyberKey, }); - const encryptedMnemonicInBase64 = btoa(encryptedMnemonic as string); await workspacesService.inviteUserToTeam({ workspaceId: workspaceId, invitedUserEmail: email, diff --git a/src/app/store/slices/sharedLinks/index.ts b/src/app/store/slices/sharedLinks/index.ts index 5863b07b2..f7cdce8c5 100644 --- a/src/app/store/slices/sharedLinks/index.ts +++ b/src/app/store/slices/sharedLinks/index.ts @@ -14,7 +14,7 @@ import notificationsService, { ToastType } from 'app/notifications/services/noti import { UserRoles } from 'app/share/types'; import { t } from 'i18next'; import userService from '../../../auth/services/user.service'; -import { encryptMessageWithPublicKey } from '../../../crypto/services/pgp.service'; +import { hybridEncryptMessageWithPublicKey } from '../../../crypto/services/pgp.service'; export interface ShareLinksState { isLoadingRoles: boolean; @@ -41,6 +41,7 @@ export interface ShareFileWithUserPayload { encryptionAlgorithm: string; roleId: string; publicKey?: string; + publicKyberKey?: string; isNewUser?: boolean; } @@ -57,24 +58,26 @@ const shareItemWithUser = createAsyncThunk Date: Mon, 13 Jan 2025 16:51:15 +0100 Subject: [PATCH 2/9] add test for InviteDialogContainer --- .../Workspace/Members/containers/InviteDialogContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx index fec88534e..695eafa78 100644 --- a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx @@ -28,7 +28,7 @@ const InviteDialogContainer = ({ isOpen, onClose }) => { return ; }; -const processInvitation = async ( +export const processInvitation = async ( user: UserSettings | null, email: string, workspaceId: string, From f304f5616a176ad74be26b2271861a9eb0cdfd91 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Mon, 13 Jan 2025 16:52:53 +0100 Subject: [PATCH 3/9] add test file --- .../containers/InviteDialogContainer.test.tsx | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx new file mode 100644 index 000000000..e525c303d --- /dev/null +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx @@ -0,0 +1,122 @@ +/** + * @jest-environment jsdom + */ +import { describe, expect, it, vi, beforeEach, beforeAll } from 'vitest'; +import { processInvitation } from './InviteDialogContainer'; +import { generateNewKeys, hybridDecryptMessageWithPrivateKey } from '../../../../../crypto/services/pgp.service'; +import navigationService from '../../../../../core/services/navigation.service'; +import workspacesService from '../../../../../core/services/workspace.service'; +import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; +import userService from '../../../../../auth/services/user.service'; +import { Buffer } from 'buffer'; + +describe('Encryption and Decryption', () => { + beforeAll(() => { + vi.mock('../../../../../core/services/navigation.service', () => ({ + default: { push: vi.fn() }, + })); + vi.mock('../../../../../core/services/workspace.service', () => ({ + default: { + inviteUserToTeam: vi.fn(), + }, + })); + vi.mock('../../../../../auth/services/user.service', () => ({ + default: { + getPublicKeyByEmail: vi.fn(), + preCreateUser: vi.fn(), + }, + })); + + vi.mock('../../../../../store', () => ({ + RootState: vi.fn(), + })); + vi.mock('../InviteDialog', () => ({ + default: { + UserInviteDialog: vi.fn(), + }, + })); + vi.mock('react-redux', () => ({ + useSelector: vi.fn(), + })); + vi.mock('../../../../../core/types', () => ({ + AppView: vi.fn(), + })); + + vi.mock('../../../../../notifications/services/notifications.service', () => ({ + default: { + show: vi.fn(), + }, + ToastType: { + Error: 'ERROR', + }, + })); + vi.mock('../../../../../core/services/error.service', () => ({ + default: { + castError: vi.fn().mockImplementation((e) => ({ message: e.message || 'Default error message' })), + reportError: vi.fn(), + }, + })); + }); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should setup workspace and encrypt mnemonic with kyber', async () => { + const keys = await generateNewKeys(); + const mockUser: Partial = { + uuid: 'mock-uuid', + email: 'mockemail@test.com', + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + keys: { + ecc: { + publicKey: keys.publicKeyArmored, + privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + }, + kyber: { + publicKey: keys.publicKyberKeyBase64, + privateKeyEncrypted: keys.privateKyberKeyBase64, + }, + }, + }; + const user = mockUser as UserSettings; + const mockEmail = user.email; + const mockWorkspaceId = 'mock-workspaceId'; + const mockMessageText = 'mock-messageText'; + + const mockWorkspacesService = { + inviteUserToTeam: vi.fn(), + }; + + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam); + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: user.keys.ecc.publicKey, + publicKyberKey: user.keys?.kyber.publicKey, + user: { + uuid: user.userId, + email: user.email, + }, + }), + ); + + await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText); + + const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0]; + expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined(); + + const { encryptedMnemonicInBase64 } = workspacesServiceInfo; + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptedMnemonicInBase64, + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKyberKeyInBase64: keys.privateKyberKeyBase64, + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + }); +}); From 1f4aa5117fb75370d752568f06fd004517ba661d Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Mon, 13 Jan 2025 17:55:33 +0100 Subject: [PATCH 4/9] add more tests --- .../containers/InviteDialogContainer.test.tsx | 105 +++++++++++++++++- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx index e525c303d..2d84b37cd 100644 --- a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx @@ -3,7 +3,11 @@ */ import { describe, expect, it, vi, beforeEach, beforeAll } from 'vitest'; import { processInvitation } from './InviteDialogContainer'; -import { generateNewKeys, hybridDecryptMessageWithPrivateKey } from '../../../../../crypto/services/pgp.service'; +import { + generateNewKeys, + hybridDecryptMessageWithPrivateKey, + decryptMessageWithPrivateKey, +} from '../../../../../crypto/services/pgp.service'; import navigationService from '../../../../../core/services/navigation.service'; import workspacesService from '../../../../../core/services/workspace.service'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; @@ -62,7 +66,7 @@ describe('Encryption and Decryption', () => { vi.clearAllMocks(); }); - it('should setup workspace and encrypt mnemonic with kyber', async () => { + async function getMockUser() { const keys = await generateNewKeys(); const mockUser: Partial = { uuid: 'mock-uuid', @@ -80,7 +84,15 @@ describe('Encryption and Decryption', () => { }, }, }; - const user = mockUser as UserSettings; + return { + user: mockUser as UserSettings, + privateKey: keys.privateKeyArmored, + privateKyberKey: keys.privateKyberKeyBase64, + }; + } + + it('should process invitation and encrypt mnemonic with kyber for existing user', async () => { + const { user, privateKey, privateKyberKey } = await getMockUser(); const mockEmail = user.email; const mockWorkspaceId = 'mock-workspaceId'; const mockMessageText = 'mock-messageText'; @@ -113,10 +125,91 @@ describe('Encryption and Decryption', () => { const { encryptedMnemonicInBase64 } = workspacesServiceInfo; const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ encryptedMessageInBase64: encryptedMnemonicInBase64, - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: keys.privateKyberKeyBase64, + privateKeyInBase64: Buffer.from(privateKey).toString('base64'), + privateKyberKeyInBase64: privateKyberKey, + }); + + expect(decryptedMessage).toEqual(user.mnemonic); + }); + + it('should process invitation and encrypt mnemonic without kyber for existing user', async () => { + const { user, privateKey } = await getMockUser(); + const mockEmail = user.email; + const mockWorkspaceId = 'mock-workspaceId'; + const mockMessageText = 'mock-messageText'; + + const mockWorkspacesService = { + inviteUserToTeam: vi.fn(), + }; + + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam); + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: '' }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: user.keys.ecc.publicKey, + publicKyberKey: user.keys?.kyber.publicKey, + user: { + uuid: user.userId, + email: user.email, + }, + }), + ); + + await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText); + + const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0]; + expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined(); + + const { encryptedMnemonicInBase64 } = workspacesServiceInfo; + const decryptedMessage = await decryptMessageWithPrivateKey({ + encryptedMessage: atob(encryptedMnemonicInBase64), + privateKeyInBase64: Buffer.from(privateKey).toString('base64'), + }); + + expect(decryptedMessage).toEqual(user.mnemonic); + }); + + it('should process invitation and encrypt mnemonic with kyber for new user', async () => { + const { user, privateKey, privateKyberKey } = await getMockUser(); + const mockEmail = user.email; + const mockWorkspaceId = 'mock-workspaceId'; + const mockMessageText = 'mock-messageText'; + + const mockWorkspacesService = { + inviteUserToTeam: vi.fn(), + }; + + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam); + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: '', publicKyberKey: '' }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: user.keys.ecc.publicKey, + publicKyberKey: user.keys?.kyber.publicKey, + user: { + uuid: user.userId, + email: user.email, + }, + }), + ); + + await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText); + + const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0]; + expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined(); + + const { encryptedMnemonicInBase64 } = workspacesServiceInfo; + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptedMnemonicInBase64, + privateKeyInBase64: Buffer.from(privateKey).toString('base64'), + privateKyberKeyInBase64: privateKyberKey, }); - expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(decryptedMessage).toEqual(user.mnemonic); }); }); From 713d46c81fe95d452b368dbd6eb28ce8e4cdc793 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Tue, 14 Jan 2025 09:59:20 +0100 Subject: [PATCH 5/9] add test draft --- .../store/slices/sharedLinks/index.test.ts | 149 ++++++++++++++++++ src/app/store/slices/sharedLinks/index.ts | 22 ++- 2 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 src/app/store/slices/sharedLinks/index.test.ts diff --git a/src/app/store/slices/sharedLinks/index.test.ts b/src/app/store/slices/sharedLinks/index.test.ts new file mode 100644 index 000000000..c5969c613 --- /dev/null +++ b/src/app/store/slices/sharedLinks/index.test.ts @@ -0,0 +1,149 @@ +/** + * @jest-environment jsdom + */ +import { describe, expect, it, vi, beforeEach, beforeAll } from 'vitest'; +import { sharedThunks, ShareFileWithUserPayload } from './index'; +const { shareItemWithUser } = sharedThunks; +import { generateNewKeys, hybridDecryptMessageWithPrivateKey } from '../../../crypto/services/pgp.service'; +import navigationService from 'app/core/services/navigation.service'; +import { RootState } from '../..'; +import { Buffer } from 'buffer'; +import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; +import userService from '../../../auth/services/user.service'; +import * as shareService from 'app/share/services/share.service'; + +describe('Encryption and Decryption', () => { + beforeAll(() => { + vi.mock('app/core/types', () => ({ + AppView: vi.fn(), + })); + vi.mock('app/core/services/navigation.service', () => ({ + default: { push: vi.fn() }, + })); + vi.mock('app/share/services/share.service', () => ({ + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + })); + vi.mock('../../../auth/services/user.service', () => ({ + default: { + getPublicKeyByEmail: vi.fn(), + preCreateUser: vi.fn(), + }, + })); + + vi.mock('../../../notifications/services/notifications.service', () => ({ + default: { + show: vi.fn(), + }, + ToastType: { + Error: 'ERROR', + }, + })); + vi.mock('app/core/services/error.service', () => ({ + default: { + castError: vi.fn().mockImplementation((e) => ({ message: e.message || 'Default error message' })), + reportError: vi.fn(), + }, + })); + }); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should setup workspace and encrypt mnemonic with kyber', async () => { + const keys = await generateNewKeys(); + const mockPayload: ShareFileWithUserPayload = { + publicKey: keys.publicKeyArmored, + publicKyberKey: keys.publicKyberKeyBase64, + isNewUser: false, + itemId: 'mock-itemId', + itemType: 'file', + notifyUser: false, + notificationMessage: 'mock-notificationMessage', + sharedWith: 'mock-sharedWith', + encryptionAlgorithm: 'mock-encryptionAlgorithm', + roleId: 'mock-roleId', + }; + + const mockUser: Partial = { + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + keys: { + ecc: { + publicKey: keys.publicKeyArmored, + privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + }, + kyber: { + publicKey: keys.publicKyberKeyBase64, + privateKeyEncrypted: keys.privateKyberKeyBase64, + }, + }, + }; + + const mockRootState: Partial = { + user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, + }; + + const user = mockUser as UserSettings; + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + + const mockShareService = { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }; + + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( + mockShareService.getSharedFolderInvitationsAsInvitedUser, + ); + vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); + vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); + + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: user.keys.ecc.publicKey, + publicKyberKey: user.keys?.kyber.publicKey, + user: { + uuid: user.userId, + email: user.email, + }, + }), + ); + + const getStateMock = vi.fn(() => mockRootState as RootState); + const dispatchMock = vi.fn(); + + const thunk = shareItemWithUser(mockPayload); + await thunk(dispatchMock, getStateMock, undefined); + + const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; + expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); + + const { encryptionKey = '' } = inviteUserToSharedFolderInput; + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptionKey, + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKyberKeyInBase64: keys.privateKyberKeyBase64, + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( + expect.objectContaining({ + itemId: mockPayload.itemId, + itemType: mockPayload.itemType, + sharedWith: mockPayload.sharedWith, + notifyUser: mockPayload.notifyUser, + notificationMessage: mockPayload.notificationMessage, + encryptionKey: encryptionKey, + encryptionAlgorithm: mockPayload.encryptionAlgorithm, + roleId: mockPayload.roleId, + persistPreviousSharing: true, + }), + ); + }); +}); diff --git a/src/app/store/slices/sharedLinks/index.ts b/src/app/store/slices/sharedLinks/index.ts index f7cdce8c5..db5d3ca2d 100644 --- a/src/app/store/slices/sharedLinks/index.ts +++ b/src/app/store/slices/sharedLinks/index.ts @@ -1,9 +1,5 @@ import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import shareService, { - getSharedFolderInvitationsAsInvitedUser, - getSharingRoles, - inviteUserToSharedFolder, -} from 'app/share/services/share.service'; +import * as shareService from 'app/share/services/share.service'; import { RootState } from '../..'; import { Role, SharedFoldersInvitationsAsInvitedUserResponse } from '@internxt/sdk/dist/drive/share/types'; @@ -78,7 +74,7 @@ const shareItemWithUser = createAsyncThunk Date: Tue, 14 Jan 2025 11:26:23 +0100 Subject: [PATCH 6/9] fix index tests and add more test cases --- src/app/share/services/share.service.ts | 3 + .../store/slices/sharedLinks/index.test.ts | 306 +++++++++++++++++- src/app/store/slices/sharedLinks/index.ts | 20 +- 3 files changed, 311 insertions(+), 18 deletions(-) diff --git a/src/app/share/services/share.service.ts b/src/app/share/services/share.service.ts index a47c6c42c..8a9c21d93 100644 --- a/src/app/share/services/share.service.ts +++ b/src/app/share/services/share.service.ts @@ -867,6 +867,9 @@ const shareService = { validateSharingInvitation, getPublicSharedItemInfo, getSharedFolderSize, + inviteUserToSharedFolder, + getSharedFolderInvitationsAsInvitedUser, + getSharingRoles, }; export default shareService; diff --git a/src/app/store/slices/sharedLinks/index.test.ts b/src/app/store/slices/sharedLinks/index.test.ts index c5969c613..b9c1426b6 100644 --- a/src/app/store/slices/sharedLinks/index.test.ts +++ b/src/app/store/slices/sharedLinks/index.test.ts @@ -4,13 +4,17 @@ import { describe, expect, it, vi, beforeEach, beforeAll } from 'vitest'; import { sharedThunks, ShareFileWithUserPayload } from './index'; const { shareItemWithUser } = sharedThunks; -import { generateNewKeys, hybridDecryptMessageWithPrivateKey } from '../../../crypto/services/pgp.service'; +import { + generateNewKeys, + hybridDecryptMessageWithPrivateKey, + decryptMessageWithPrivateKey, +} from '../../../crypto/services/pgp.service'; import navigationService from 'app/core/services/navigation.service'; import { RootState } from '../..'; import { Buffer } from 'buffer'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import userService from '../../../auth/services/user.service'; -import * as shareService from 'app/share/services/share.service'; +import shareService from 'app/share/services/share.service'; describe('Encryption and Decryption', () => { beforeAll(() => { @@ -21,9 +25,11 @@ describe('Encryption and Decryption', () => { default: { push: vi.fn() }, })); vi.mock('app/share/services/share.service', () => ({ - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), + default: { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }, })); vi.mock('../../../auth/services/user.service', () => ({ default: { @@ -52,7 +58,7 @@ describe('Encryption and Decryption', () => { vi.clearAllMocks(); }); - it('should setup workspace and encrypt mnemonic with kyber', async () => { + it('shareItemWithUser encrypts with kyber for an existing user', async () => { const keys = await generateNewKeys(); const mockPayload: ShareFileWithUserPayload = { publicKey: keys.publicKeyArmored, @@ -94,7 +100,7 @@ describe('Encryption and Decryption', () => { getSharedFolderInvitationsAsInvitedUser: vi.fn(), getSharingRoles: vi.fn(), }; - + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( mockShareService.getSharedFolderInvitationsAsInvitedUser, ); @@ -102,7 +108,196 @@ describe('Encryption and Decryption', () => { vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }), + Promise.resolve({ publicKey: '', publicKyberKey: '' }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: '', + publicKyberKey: '', + user: { + uuid: '', + email: '', + }, + }), + ); + + const getStateMock = vi.fn(() => mockRootState as RootState); + const dispatchMock = vi.fn(); + + const thunk = shareItemWithUser(mockPayload); + await thunk(dispatchMock, getStateMock, undefined); + + const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; + expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); + + const { encryptionKey = '' } = inviteUserToSharedFolderInput; + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptionKey, + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKyberKeyInBase64: keys.privateKyberKeyBase64, + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( + expect.objectContaining({ + itemId: mockPayload.itemId, + itemType: mockPayload.itemType, + sharedWith: mockPayload.sharedWith, + notifyUser: mockPayload.notifyUser, + notificationMessage: mockPayload.notificationMessage, + encryptionKey: encryptionKey, + encryptionAlgorithm: mockPayload.encryptionAlgorithm, + roleId: mockPayload.roleId, + persistPreviousSharing: true, + }), + ); + }); + + it('shareItemWithUser encrypts without kyber for an existing user', async () => { + const keys = await generateNewKeys(); + const mockPayload: ShareFileWithUserPayload = { + publicKey: keys.publicKeyArmored, + publicKyberKey: '', + isNewUser: false, + itemId: 'mock-itemId', + itemType: 'file', + notifyUser: false, + notificationMessage: 'mock-notificationMessage', + sharedWith: 'mock-sharedWith', + encryptionAlgorithm: 'mock-encryptionAlgorithm', + roleId: 'mock-roleId', + }; + + const mockUser: Partial = { + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + keys: { + ecc: { + publicKey: keys.publicKeyArmored, + privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + }, + kyber: { + publicKey: keys.publicKyberKeyBase64, + privateKeyEncrypted: keys.privateKyberKeyBase64, + }, + }, + }; + + const mockRootState: Partial = { + user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, + }; + + const user = mockUser as UserSettings; + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + + const mockShareService = { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }; + + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( + mockShareService.getSharedFolderInvitationsAsInvitedUser, + ); + vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); + vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); + + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: '', publicKyberKey: '' }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: '', + publicKyberKey: '', + user: { + uuid: '', + email: '', + }, + }), + ); + + const getStateMock = vi.fn(() => mockRootState as RootState); + const dispatchMock = vi.fn(); + + const thunk = shareItemWithUser(mockPayload); + await thunk(dispatchMock, getStateMock, undefined); + + const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; + expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); + + const { encryptionKey = '' } = inviteUserToSharedFolderInput; + const decryptedMessage = await decryptMessageWithPrivateKey({ + encryptedMessage: atob(encryptionKey), + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( + expect.objectContaining({ + itemId: mockPayload.itemId, + itemType: mockPayload.itemType, + sharedWith: mockPayload.sharedWith, + notifyUser: mockPayload.notifyUser, + notificationMessage: mockPayload.notificationMessage, + encryptionKey: encryptionKey, + encryptionAlgorithm: mockPayload.encryptionAlgorithm, + roleId: mockPayload.roleId, + persistPreviousSharing: true, + }), + ); + }); + + it('shareItemWithUser encrypts with kyber for an new user', async () => { + const keys = await generateNewKeys(); + const mockPayload: ShareFileWithUserPayload = { + publicKey: '', + publicKyberKey: '', + isNewUser: true, + itemId: 'mock-itemId', + itemType: 'file', + notifyUser: false, + notificationMessage: 'mock-notificationMessage', + sharedWith: 'mock-sharedWith', + encryptionAlgorithm: 'mock-encryptionAlgorithm', + roleId: 'mock-roleId', + }; + + const mockUser: Partial = { + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + keys: { + ecc: { + publicKey: keys.publicKeyArmored, + privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + }, + kyber: { + publicKey: keys.publicKyberKeyBase64, + privateKeyEncrypted: keys.privateKyberKeyBase64, + }, + }, + }; + + const mockRootState: Partial = { + user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, + }; + + const user = mockUser as UserSettings; + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + + const mockShareService = { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }; + + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( + mockShareService.getSharedFolderInvitationsAsInvitedUser, + ); + vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); + vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); + + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: '', publicKyberKey: '' }), ); vi.spyOn(userService, 'preCreateUser').mockReturnValue( Promise.resolve({ @@ -146,4 +341,99 @@ describe('Encryption and Decryption', () => { }), ); }); + + it('shareItemWithUser encrypts with kyber key obtained via getPublicKeyByEmail ', async () => { + const keys = await generateNewKeys(); + const mockPayload: ShareFileWithUserPayload = { + publicKey: '', + publicKyberKey: '', + isNewUser: false, + itemId: 'mock-itemId', + itemType: 'file', + notifyUser: false, + notificationMessage: 'mock-notificationMessage', + sharedWith: 'mock-sharedWith', + encryptionAlgorithm: 'mock-encryptionAlgorithm', + roleId: 'mock-roleId', + }; + + const mockUser: Partial = { + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + keys: { + ecc: { + publicKey: keys.publicKeyArmored, + privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + }, + kyber: { + publicKey: keys.publicKyberKeyBase64, + privateKeyEncrypted: keys.privateKyberKeyBase64, + }, + }, + }; + + const mockRootState: Partial = { + user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, + }; + + const user = mockUser as UserSettings; + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + + const mockShareService = { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }; + + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( + mockShareService.getSharedFolderInvitationsAsInvitedUser, + ); + vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); + vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); + + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( + Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }), + ); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( + Promise.resolve({ + publicKey: '', + publicKyberKey: '', + user: { + uuid: '', + email: '', + }, + }), + ); + + const getStateMock = vi.fn(() => mockRootState as RootState); + const dispatchMock = vi.fn(); + + const thunk = shareItemWithUser(mockPayload); + await thunk(dispatchMock, getStateMock, undefined); + + const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; + expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); + + const { encryptionKey = '' } = inviteUserToSharedFolderInput; + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptionKey, + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKyberKeyInBase64: keys.privateKyberKeyBase64, + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( + expect.objectContaining({ + itemId: mockPayload.itemId, + itemType: mockPayload.itemType, + sharedWith: mockPayload.sharedWith, + notifyUser: mockPayload.notifyUser, + notificationMessage: mockPayload.notificationMessage, + encryptionKey: encryptionKey, + encryptionAlgorithm: mockPayload.encryptionAlgorithm, + roleId: mockPayload.roleId, + persistPreviousSharing: true, + }), + ); + }); }); diff --git a/src/app/store/slices/sharedLinks/index.ts b/src/app/store/slices/sharedLinks/index.ts index db5d3ca2d..1436fb3d6 100644 --- a/src/app/store/slices/sharedLinks/index.ts +++ b/src/app/store/slices/sharedLinks/index.ts @@ -1,5 +1,5 @@ import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import * as shareService from 'app/share/services/share.service'; +import shareService from 'app/share/services/share.service'; import { RootState } from '../..'; import { Role, SharedFoldersInvitationsAsInvitedUserResponse } from '@internxt/sdk/dist/drive/share/types'; @@ -103,7 +103,7 @@ const shareItemWithUser = createAsyncThunk => { try { - const newRoles = await getSharingRoles(); + const newRoles = await shareService.getSharingRoles(); if (newRoles.length > 0) { dispatch(sharedActions.setSharedFolderUserRoles(newRoles)); @@ -185,7 +185,7 @@ const getPendingInvitations = createAsyncThunk => { try { - const pendingInvitations = await getSharedFolderInvitationsAsInvitedUser({}); + const pendingInvitations = await shareService.getSharedFolderInvitationsAsInvitedUser({}); dispatch(sharedActions.setPendingInvitations(pendingInvitations.invites)); } catch (err: unknown) { @@ -241,14 +241,14 @@ export const sharedSelectors = { }, }; -export const sharedActions = sharedSlice.actions; */ +export const sharedActions = sharedSlice.actions; export const sharedThunks = { shareItemWithUser, - //stopSharingItem, - //removeUserFromSharedFolder, - //getSharedFolderRoles, - //getPendingInvitations, + stopSharingItem, + removeUserFromSharedFolder, + getSharedFolderRoles, + getPendingInvitations, }; -//export default sharedSlice.reducer; +export default sharedSlice.reducer; From bd406246a3055dc0dcb43d84d0d87ab4f40a953a Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Tue, 14 Jan 2025 18:40:20 +0100 Subject: [PATCH 7/9] upgrade to sdk 1.9.3 --- package.json | 2 +- .../containers/InviteDialogContainer.test.tsx | 4 ++-- src/app/share/services/share.service.test.ts | 4 ++-- src/app/share/services/share.service.ts | 4 ++-- src/app/store/slices/sharedLinks/index.test.ts | 16 ++++++++-------- yarn.lock | 8 ++++---- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 7c451c1d4..a6fbeb947 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@internxt/inxt-js": "=1.2.21", "@internxt/lib": "^1.2.0", "@internxt/ui": "0.0.10", - "@internxt/sdk": "=1.9.1", + "@internxt/sdk": "=1.9.3", "@phosphor-icons/react": "^2.1.7", "@popperjs/core": "^2.11.6", "@reduxjs/toolkit": "^1.6.0", diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx index 2d84b37cd..77b69c4cd 100644 --- a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx @@ -76,11 +76,11 @@ describe('Encryption and Decryption', () => { keys: { ecc: { publicKey: keys.publicKeyArmored, - privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), }, kyber: { publicKey: keys.publicKyberKeyBase64, - privateKeyEncrypted: keys.privateKyberKeyBase64, + privateKey: keys.privateKyberKeyBase64, }, }, }; diff --git a/src/app/share/services/share.service.test.ts b/src/app/share/services/share.service.test.ts index 778ca6636..08bf8aef5 100644 --- a/src/app/share/services/share.service.test.ts +++ b/src/app/share/services/share.service.test.ts @@ -89,11 +89,11 @@ describe('Encryption and Decryption', () => { keys: { ecc: { publicKey: keys.publicKeyArmored, - privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), }, kyber: { publicKey: keys.publicKyberKeyBase64, - privateKeyEncrypted: keys.privateKyberKeyBase64, + privateKey: keys.privateKyberKeyBase64, }, }, appSumoDetails: null, diff --git a/src/app/share/services/share.service.ts b/src/app/share/services/share.service.ts index 8a9c21d93..d81fd14b7 100644 --- a/src/app/share/services/share.service.ts +++ b/src/app/share/services/share.service.ts @@ -593,8 +593,8 @@ export const decryptMnemonic = async (encryptionKey: string): Promise { keys: { ecc: { publicKey: keys.publicKeyArmored, - privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), }, kyber: { publicKey: keys.publicKyberKeyBase64, - privateKeyEncrypted: keys.privateKyberKeyBase64, + privateKey: keys.privateKyberKeyBase64, }, }, }; @@ -174,11 +174,11 @@ describe('Encryption and Decryption', () => { keys: { ecc: { publicKey: keys.publicKeyArmored, - privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), }, kyber: { publicKey: keys.publicKyberKeyBase64, - privateKeyEncrypted: keys.privateKyberKeyBase64, + privateKey: keys.privateKyberKeyBase64, }, }, }; @@ -268,11 +268,11 @@ describe('Encryption and Decryption', () => { keys: { ecc: { publicKey: keys.publicKeyArmored, - privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), }, kyber: { publicKey: keys.publicKyberKeyBase64, - privateKeyEncrypted: keys.privateKyberKeyBase64, + privateKey: keys.privateKyberKeyBase64, }, }, }; @@ -363,11 +363,11 @@ describe('Encryption and Decryption', () => { keys: { ecc: { publicKey: keys.publicKeyArmored, - privateKeyEncrypted: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), }, kyber: { publicKey: keys.publicKyberKeyBase64, - privateKeyEncrypted: keys.privateKyberKeyBase64, + privateKey: keys.privateKyberKeyBase64, }, }, }; diff --git a/yarn.lock b/yarn.lock index 12b670973..ed95926fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2014,10 +2014,10 @@ resolved "https://npm.pkg.github.com/download/@internxt/prettier-config/1.0.2/5bd220b8de76734448db5475b3e0c01f9d22c19b#5bd220b8de76734448db5475b3e0c01f9d22c19b" integrity sha512-t4HiqvCbC7XgQepwWlIaFJe3iwW7HCf6xOSU9nKTV0tiGqOPz7xMtIgLEloQrDA34Cx4PkOYBXrvFPV6RxSFAA== -"@internxt/sdk@=1.9.1": - version "1.9.1" - resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.9.1/f78fe79c261782bb2ff181977558ba25fd546aca#f78fe79c261782bb2ff181977558ba25fd546aca" - integrity sha512-4FgFjlLqcPy5btZ3BNsLjFSkot0giTA6UgbFzIS7mtvpNAD12ce3l7Ok7uhAvcs9ujy9TMr0cW/aj5HyrY70rg== +"@internxt/sdk@=1.9.3": + version "1.9.3" + resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.9.3/d8076550a1a61d1db2a67d5d378dc0785edd8e98#d8076550a1a61d1db2a67d5d378dc0785edd8e98" + integrity sha512-mHV/M9Hf8NLtRoYKE9LtgGAGdIkB38rWkEnXRgwrbFF7U9F49zIBX6JwFm5bYz3XXVzZaPsUNcR30AifLxGrow== dependencies: axios "^0.24.0" query-string "^7.1.0" From 8cc58a35e1ee7651168d347e6207da7e2119a568 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Fri, 24 Jan 2025 11:14:07 +0100 Subject: [PATCH 8/9] add more tests --- .../containers/InviteDialogContainer.test.tsx | 76 ++++-- .../containers/InviteDialogContainer.tsx | 4 +- .../store/slices/sharedLinks/index.test.ts | 236 +++++++++++------- src/app/store/slices/sharedLinks/index.ts | 6 +- 4 files changed, 210 insertions(+), 112 deletions(-) diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx index 77b69c4cd..fb4282627 100644 --- a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.test.tsx @@ -91,6 +91,22 @@ describe('Encryption and Decryption', () => { }; } + async function getMockOldUser() { + const keys = await generateNewKeys(); + const mockUser: Partial = { + uuid: 'mock-uuid', + email: 'mockemail@test.com', + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + publicKey: keys.publicKeyArmored, + privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), + }; + return { + user: mockUser as UserSettings, + privateKey: keys.privateKeyArmored, + }; + } + it('should process invitation and encrypt mnemonic with kyber for existing user', async () => { const { user, privateKey, privateKyberKey } = await getMockUser(); const mockEmail = user.email; @@ -106,16 +122,6 @@ describe('Encryption and Decryption', () => { vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }), ); - vi.spyOn(userService, 'preCreateUser').mockReturnValue( - Promise.resolve({ - publicKey: user.keys.ecc.publicKey, - publicKyberKey: user.keys?.kyber.publicKey, - user: { - uuid: user.userId, - email: user.email, - }, - }), - ); await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText); @@ -133,7 +139,35 @@ describe('Encryption and Decryption', () => { }); it('should process invitation and encrypt mnemonic without kyber for existing user', async () => { - const { user, privateKey } = await getMockUser(); + const { user, privateKey } = await getMockOldUser(); + const mockEmail = user.email; + const mockWorkspaceId = 'mock-workspaceId'; + const mockMessageText = 'mock-messageText'; + + const mockWorkspacesService = { + inviteUserToTeam: vi.fn(), + }; + + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam); + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(Promise.resolve({ publicKey: user.publicKey })); + + await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText); + + const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0]; + expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined(); + + const { encryptedMnemonicInBase64 } = workspacesServiceInfo; + const decryptedMessage = await decryptMessageWithPrivateKey({ + encryptedMessage: atob(encryptedMnemonicInBase64), + privateKeyInBase64: Buffer.from(privateKey).toString('base64'), + }); + + expect(decryptedMessage).toEqual(user.mnemonic); + }); + + it('should process invitation and encrypt mnemonic with kyber for new user', async () => { + const { user, privateKey, privateKyberKey } = await getMockUser(); const mockEmail = user.email; const mockWorkspaceId = 'mock-workspaceId'; const mockMessageText = 'mock-messageText'; @@ -145,7 +179,7 @@ describe('Encryption and Decryption', () => { vi.spyOn(navigationService, 'push').mockImplementation(() => {}); vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam); vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: '' }), + Promise.resolve({ publicKey: '', publicKyberKey: '' }), ); vi.spyOn(userService, 'preCreateUser').mockReturnValue( Promise.resolve({ @@ -164,16 +198,17 @@ describe('Encryption and Decryption', () => { expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined(); const { encryptedMnemonicInBase64 } = workspacesServiceInfo; - const decryptedMessage = await decryptMessageWithPrivateKey({ - encryptedMessage: atob(encryptedMnemonicInBase64), + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptedMnemonicInBase64, privateKeyInBase64: Buffer.from(privateKey).toString('base64'), + privateKyberKeyInBase64: privateKyberKey, }); expect(decryptedMessage).toEqual(user.mnemonic); }); - it('should process invitation and encrypt mnemonic with kyber for new user', async () => { - const { user, privateKey, privateKyberKey } = await getMockUser(); + it('should process invitation and encrypt mnemonic for new user without Kyber', async () => { + const { user, privateKey } = await getMockOldUser(); const mockEmail = user.email; const mockWorkspaceId = 'mock-workspaceId'; const mockMessageText = 'mock-messageText'; @@ -184,13 +219,10 @@ describe('Encryption and Decryption', () => { vi.spyOn(navigationService, 'push').mockImplementation(() => {}); vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam); - vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: '', publicKyberKey: '' }), - ); + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(Promise.resolve({ publicKey: '' })); vi.spyOn(userService, 'preCreateUser').mockReturnValue( Promise.resolve({ - publicKey: user.keys.ecc.publicKey, - publicKyberKey: user.keys?.kyber.publicKey, + publicKey: user.publicKey, user: { uuid: user.userId, email: user.email, @@ -207,7 +239,7 @@ describe('Encryption and Decryption', () => { const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ encryptedMessageInBase64: encryptedMnemonicInBase64, privateKeyInBase64: Buffer.from(privateKey).toString('base64'), - privateKyberKeyInBase64: privateKyberKey, + privateKyberKeyInBase64: '', }); expect(decryptedMessage).toEqual(user.mnemonic); diff --git a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx index 695eafa78..69c7ad2eb 100644 --- a/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx +++ b/src/app/newSettings/Sections/Workspace/Members/containers/InviteDialogContainer.tsx @@ -44,7 +44,7 @@ export const processInvitation = async ( try { const publicKeyResponse = await userService.getPublicKeyByEmail(email); publicKey = publicKeyResponse.publicKey; - publicKyberKey = publicKeyResponse.publicKyberKey; + publicKyberKey = publicKeyResponse.publicKyberKey ?? ''; } catch (err) { console.log(err); } @@ -53,7 +53,7 @@ export const processInvitation = async ( if (isNewUser) { const preCreatedUserResponse = await userService.preCreateUser(email); publicKey = preCreatedUserResponse.publicKey; - publicKyberKey = preCreatedUserResponse.publicKyberKey; + publicKyberKey = preCreatedUserResponse.publicKyberKey ?? ''; } const encryptedMnemonicInBase64 = await hybridEncryptMessageWithPublicKey({ diff --git a/src/app/store/slices/sharedLinks/index.test.ts b/src/app/store/slices/sharedLinks/index.test.ts index 5c79c231e..32e44af88 100644 --- a/src/app/store/slices/sharedLinks/index.test.ts +++ b/src/app/store/slices/sharedLinks/index.test.ts @@ -76,23 +76,12 @@ describe('Encryption and Decryption', () => { const mockUser: Partial = { mnemonic: 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - keys: { - ecc: { - publicKey: keys.publicKeyArmored, - privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), - }, - kyber: { - publicKey: keys.publicKyberKeyBase64, - privateKey: keys.privateKyberKeyBase64, - }, - }, }; const mockRootState: Partial = { user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, }; - const user = mockUser as UserSettings; vi.spyOn(navigationService, 'push').mockImplementation(() => {}); const mockShareService = { @@ -107,20 +96,6 @@ describe('Encryption and Decryption', () => { vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: '', publicKyberKey: '' }), - ); - vi.spyOn(userService, 'preCreateUser').mockReturnValue( - Promise.resolve({ - publicKey: '', - publicKyberKey: '', - user: { - uuid: '', - email: '', - }, - }), - ); - const getStateMock = vi.fn(() => mockRootState as RootState); const dispatchMock = vi.fn(); @@ -157,7 +132,6 @@ describe('Encryption and Decryption', () => { const keys = await generateNewKeys(); const mockPayload: ShareFileWithUserPayload = { publicKey: keys.publicKeyArmored, - publicKyberKey: '', isNewUser: false, itemId: 'mock-itemId', itemType: 'file', @@ -171,23 +145,12 @@ describe('Encryption and Decryption', () => { const mockUser: Partial = { mnemonic: 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - keys: { - ecc: { - publicKey: keys.publicKeyArmored, - privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), - }, - kyber: { - publicKey: keys.publicKyberKeyBase64, - privateKey: keys.privateKyberKeyBase64, - }, - }, }; const mockRootState: Partial = { user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, }; - const user = mockUser as UserSettings; vi.spyOn(navigationService, 'push').mockImplementation(() => {}); const mockShareService = { @@ -202,16 +165,83 @@ describe('Encryption and Decryption', () => { vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: '', publicKyberKey: '' }), + const getStateMock = vi.fn(() => mockRootState as RootState); + const dispatchMock = vi.fn(); + + const thunk = shareItemWithUser(mockPayload); + await thunk(dispatchMock, getStateMock, undefined); + + const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; + expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); + + const { encryptionKey = '' } = inviteUserToSharedFolderInput; + const decryptedMessage = await decryptMessageWithPrivateKey({ + encryptedMessage: atob(encryptionKey), + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( + expect.objectContaining({ + itemId: mockPayload.itemId, + itemType: mockPayload.itemType, + sharedWith: mockPayload.sharedWith, + notifyUser: mockPayload.notifyUser, + notificationMessage: mockPayload.notificationMessage, + encryptionKey: encryptionKey, + encryptionAlgorithm: mockPayload.encryptionAlgorithm, + roleId: mockPayload.roleId, + persistPreviousSharing: true, + }), ); + }); + + it('shareItemWithUser encrypts with kyber for an new user', async () => { + const keys = await generateNewKeys(); + const mockPayload: ShareFileWithUserPayload = { + publicKey: '', + publicKyberKey: '', + isNewUser: true, + itemId: 'mock-itemId', + itemType: 'file', + notifyUser: false, + notificationMessage: 'mock-notificationMessage', + sharedWith: 'mock-sharedWith', + encryptionAlgorithm: 'mock-encryptionAlgorithm', + roleId: 'mock-roleId', + }; + + const mockUser: Partial = { + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + }; + + const mockRootState: Partial = { + user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, + }; + + const user = mockUser as UserSettings; + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + + const mockShareService = { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }; + + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( + mockShareService.getSharedFolderInvitationsAsInvitedUser, + ); + vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); + vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); + vi.spyOn(userService, 'preCreateUser').mockReturnValue( Promise.resolve({ - publicKey: '', - publicKyberKey: '', + publicKey: keys.publicKeyArmored, + publicKyberKey: keys.publicKyberKeyBase64, user: { - uuid: '', - email: '', + uuid: user.userId, + email: user.email, }, }), ); @@ -226,9 +256,10 @@ describe('Encryption and Decryption', () => { expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await decryptMessageWithPrivateKey({ - encryptedMessage: atob(encryptionKey), + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptionKey, privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKyberKeyInBase64: keys.privateKyberKeyBase64, }); expect(decryptedMessage).toEqual(mockUser.mnemonic); @@ -247,11 +278,10 @@ describe('Encryption and Decryption', () => { ); }); - it('shareItemWithUser encrypts with kyber for an new user', async () => { + it('shareItemWithUser encrypts without kyber for an new user', async () => { const keys = await generateNewKeys(); const mockPayload: ShareFileWithUserPayload = { publicKey: '', - publicKyberKey: '', isNewUser: true, itemId: 'mock-itemId', itemType: 'file', @@ -265,16 +295,6 @@ describe('Encryption and Decryption', () => { const mockUser: Partial = { mnemonic: 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - keys: { - ecc: { - publicKey: keys.publicKeyArmored, - privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), - }, - kyber: { - publicKey: keys.publicKyberKeyBase64, - privateKey: keys.privateKyberKeyBase64, - }, - }, }; const mockRootState: Partial = { @@ -296,13 +316,9 @@ describe('Encryption and Decryption', () => { vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: '', publicKyberKey: '' }), - ); vi.spyOn(userService, 'preCreateUser').mockReturnValue( Promise.resolve({ - publicKey: user.keys.ecc.publicKey, - publicKyberKey: user.keys?.kyber.publicKey, + publicKey: keys.publicKeyArmored, user: { uuid: user.userId, email: user.email, @@ -323,7 +339,7 @@ describe('Encryption and Decryption', () => { const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ encryptedMessageInBase64: encryptionKey, privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: keys.privateKyberKeyBase64, + privateKyberKeyInBase64: '', }); expect(decryptedMessage).toEqual(mockUser.mnemonic); @@ -342,7 +358,7 @@ describe('Encryption and Decryption', () => { ); }); - it('shareItemWithUser encrypts with kyber key obtained via getPublicKeyByEmail ', async () => { + it('shareItemWithUser encrypts with kyber, keys obtained via getPublicKeyByEmail ', async () => { const keys = await generateNewKeys(); const mockPayload: ShareFileWithUserPayload = { publicKey: '', @@ -360,23 +376,12 @@ describe('Encryption and Decryption', () => { const mockUser: Partial = { mnemonic: 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - keys: { - ecc: { - publicKey: keys.publicKeyArmored, - privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'), - }, - kyber: { - publicKey: keys.publicKyberKeyBase64, - privateKey: keys.privateKyberKeyBase64, - }, - }, }; const mockRootState: Partial = { user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, }; - const user = mockUser as UserSettings; vi.spyOn(navigationService, 'push').mockImplementation(() => {}); const mockShareService = { @@ -392,18 +397,79 @@ describe('Encryption and Decryption', () => { vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }), + Promise.resolve({ publicKey: keys.publicKeyArmored, publicKyberKey: keys.publicKyberKeyBase64 }), ); - vi.spyOn(userService, 'preCreateUser').mockReturnValue( - Promise.resolve({ - publicKey: '', - publicKyberKey: '', - user: { - uuid: '', - email: '', - }, + + const getStateMock = vi.fn(() => mockRootState as RootState); + const dispatchMock = vi.fn(); + + const thunk = shareItemWithUser(mockPayload); + await thunk(dispatchMock, getStateMock, undefined); + + const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; + expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); + + const { encryptionKey = '' } = inviteUserToSharedFolderInput; + const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ + encryptedMessageInBase64: encryptionKey, + privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), + privateKyberKeyInBase64: keys.privateKyberKeyBase64, + }); + + expect(decryptedMessage).toEqual(mockUser.mnemonic); + expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( + expect.objectContaining({ + itemId: mockPayload.itemId, + itemType: mockPayload.itemType, + sharedWith: mockPayload.sharedWith, + notifyUser: mockPayload.notifyUser, + notificationMessage: mockPayload.notificationMessage, + encryptionKey: encryptionKey, + encryptionAlgorithm: mockPayload.encryptionAlgorithm, + roleId: mockPayload.roleId, + persistPreviousSharing: true, }), ); + }); + + it('shareItemWithUser encrypts without kyber, keys obtained via getPublicKeyByEmail ', async () => { + const keys = await generateNewKeys(); + const mockPayload: ShareFileWithUserPayload = { + publicKey: '', + isNewUser: false, + itemId: 'mock-itemId', + itemType: 'file', + notifyUser: false, + notificationMessage: 'mock-notificationMessage', + sharedWith: 'mock-sharedWith', + encryptionAlgorithm: 'mock-encryptionAlgorithm', + roleId: 'mock-roleId', + }; + + const mockUser: Partial = { + mnemonic: + 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', + }; + + const mockRootState: Partial = { + user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, + }; + + vi.spyOn(navigationService, 'push').mockImplementation(() => {}); + + const mockShareService = { + inviteUserToSharedFolder: vi.fn(), + getSharedFolderInvitationsAsInvitedUser: vi.fn(), + getSharingRoles: vi.fn(), + }; + + vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( + mockShareService.getSharedFolderInvitationsAsInvitedUser, + ); + vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); + vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); + + vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(Promise.resolve({ publicKey: keys.publicKeyArmored })); const getStateMock = vi.fn(() => mockRootState as RootState); const dispatchMock = vi.fn(); @@ -418,7 +484,7 @@ describe('Encryption and Decryption', () => { const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ encryptedMessageInBase64: encryptionKey, privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: keys.privateKyberKeyBase64, + privateKyberKeyInBase64: '', }); expect(decryptedMessage).toEqual(mockUser.mnemonic); diff --git a/src/app/store/slices/sharedLinks/index.ts b/src/app/store/slices/sharedLinks/index.ts index e27b4d6f1..36cde56dc 100644 --- a/src/app/store/slices/sharedLinks/index.ts +++ b/src/app/store/slices/sharedLinks/index.ts @@ -54,18 +54,18 @@ const shareItemWithUser = createAsyncThunk Date: Wed, 29 Jan 2025 12:28:32 +0100 Subject: [PATCH 9/9] remove index changes from this PR --- .../store/slices/sharedLinks/index.test.ts | 505 ------------------ src/app/store/slices/sharedLinks/index.ts | 23 +- 2 files changed, 12 insertions(+), 516 deletions(-) delete mode 100644 src/app/store/slices/sharedLinks/index.test.ts diff --git a/src/app/store/slices/sharedLinks/index.test.ts b/src/app/store/slices/sharedLinks/index.test.ts deleted file mode 100644 index 32e44af88..000000000 --- a/src/app/store/slices/sharedLinks/index.test.ts +++ /dev/null @@ -1,505 +0,0 @@ -/** - * @jest-environment jsdom - */ -import { describe, expect, it, vi, beforeEach, beforeAll } from 'vitest'; -import { sharedThunks, ShareFileWithUserPayload } from './index'; -const { shareItemWithUser } = sharedThunks; -import { - generateNewKeys, - hybridDecryptMessageWithPrivateKey, - decryptMessageWithPrivateKey, -} from '../../../crypto/services/pgp.service'; -import navigationService from 'app/core/services/navigation.service'; -import { RootState } from '../..'; -import { Buffer } from 'buffer'; -import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; -import userService from '../../../auth/services/user.service'; -import shareService from 'app/share/services/share.service'; - -describe('Encryption and Decryption', () => { - beforeAll(() => { - vi.mock('app/core/types', () => ({ - AppView: vi.fn(), - })); - vi.mock('app/core/services/navigation.service', () => ({ - default: { push: vi.fn() }, - })); - vi.mock('app/share/services/share.service', () => ({ - default: { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }, - })); - vi.mock('../../../auth/services/user.service', () => ({ - default: { - getPublicKeyByEmail: vi.fn(), - preCreateUser: vi.fn(), - }, - })); - - vi.mock('../../../notifications/services/notifications.service', () => ({ - default: { - show: vi.fn(), - }, - ToastType: { - Error: 'ERROR', - }, - })); - vi.mock('app/core/services/error.service', () => ({ - default: { - castError: vi.fn().mockImplementation((e) => ({ message: e.message || 'Default error message' })), - reportError: vi.fn(), - }, - })); - }); - - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('shareItemWithUser encrypts with kyber for an existing user', async () => { - const keys = await generateNewKeys(); - const mockPayload: ShareFileWithUserPayload = { - publicKey: keys.publicKeyArmored, - publicKyberKey: keys.publicKyberKeyBase64, - isNewUser: false, - itemId: 'mock-itemId', - itemType: 'file', - notifyUser: false, - notificationMessage: 'mock-notificationMessage', - sharedWith: 'mock-sharedWith', - encryptionAlgorithm: 'mock-encryptionAlgorithm', - roleId: 'mock-roleId', - }; - - const mockUser: Partial = { - mnemonic: - 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - }; - - const mockRootState: Partial = { - user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, - }; - - vi.spyOn(navigationService, 'push').mockImplementation(() => {}); - - const mockShareService = { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }; - - vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( - mockShareService.getSharedFolderInvitationsAsInvitedUser, - ); - vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); - vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - - const getStateMock = vi.fn(() => mockRootState as RootState); - const dispatchMock = vi.fn(); - - const thunk = shareItemWithUser(mockPayload); - await thunk(dispatchMock, getStateMock, undefined); - - const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; - expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); - - const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ - encryptedMessageInBase64: encryptionKey, - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: keys.privateKyberKeyBase64, - }); - - expect(decryptedMessage).toEqual(mockUser.mnemonic); - expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( - expect.objectContaining({ - itemId: mockPayload.itemId, - itemType: mockPayload.itemType, - sharedWith: mockPayload.sharedWith, - notifyUser: mockPayload.notifyUser, - notificationMessage: mockPayload.notificationMessage, - encryptionKey: encryptionKey, - encryptionAlgorithm: mockPayload.encryptionAlgorithm, - roleId: mockPayload.roleId, - persistPreviousSharing: true, - }), - ); - }); - - it('shareItemWithUser encrypts without kyber for an existing user', async () => { - const keys = await generateNewKeys(); - const mockPayload: ShareFileWithUserPayload = { - publicKey: keys.publicKeyArmored, - isNewUser: false, - itemId: 'mock-itemId', - itemType: 'file', - notifyUser: false, - notificationMessage: 'mock-notificationMessage', - sharedWith: 'mock-sharedWith', - encryptionAlgorithm: 'mock-encryptionAlgorithm', - roleId: 'mock-roleId', - }; - - const mockUser: Partial = { - mnemonic: - 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - }; - - const mockRootState: Partial = { - user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, - }; - - vi.spyOn(navigationService, 'push').mockImplementation(() => {}); - - const mockShareService = { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }; - - vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( - mockShareService.getSharedFolderInvitationsAsInvitedUser, - ); - vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); - vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - - const getStateMock = vi.fn(() => mockRootState as RootState); - const dispatchMock = vi.fn(); - - const thunk = shareItemWithUser(mockPayload); - await thunk(dispatchMock, getStateMock, undefined); - - const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; - expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); - - const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await decryptMessageWithPrivateKey({ - encryptedMessage: atob(encryptionKey), - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - }); - - expect(decryptedMessage).toEqual(mockUser.mnemonic); - expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( - expect.objectContaining({ - itemId: mockPayload.itemId, - itemType: mockPayload.itemType, - sharedWith: mockPayload.sharedWith, - notifyUser: mockPayload.notifyUser, - notificationMessage: mockPayload.notificationMessage, - encryptionKey: encryptionKey, - encryptionAlgorithm: mockPayload.encryptionAlgorithm, - roleId: mockPayload.roleId, - persistPreviousSharing: true, - }), - ); - }); - - it('shareItemWithUser encrypts with kyber for an new user', async () => { - const keys = await generateNewKeys(); - const mockPayload: ShareFileWithUserPayload = { - publicKey: '', - publicKyberKey: '', - isNewUser: true, - itemId: 'mock-itemId', - itemType: 'file', - notifyUser: false, - notificationMessage: 'mock-notificationMessage', - sharedWith: 'mock-sharedWith', - encryptionAlgorithm: 'mock-encryptionAlgorithm', - roleId: 'mock-roleId', - }; - - const mockUser: Partial = { - mnemonic: - 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - }; - - const mockRootState: Partial = { - user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, - }; - - const user = mockUser as UserSettings; - vi.spyOn(navigationService, 'push').mockImplementation(() => {}); - - const mockShareService = { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }; - - vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( - mockShareService.getSharedFolderInvitationsAsInvitedUser, - ); - vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); - vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - - vi.spyOn(userService, 'preCreateUser').mockReturnValue( - Promise.resolve({ - publicKey: keys.publicKeyArmored, - publicKyberKey: keys.publicKyberKeyBase64, - user: { - uuid: user.userId, - email: user.email, - }, - }), - ); - - const getStateMock = vi.fn(() => mockRootState as RootState); - const dispatchMock = vi.fn(); - - const thunk = shareItemWithUser(mockPayload); - await thunk(dispatchMock, getStateMock, undefined); - - const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; - expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); - - const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ - encryptedMessageInBase64: encryptionKey, - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: keys.privateKyberKeyBase64, - }); - - expect(decryptedMessage).toEqual(mockUser.mnemonic); - expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( - expect.objectContaining({ - itemId: mockPayload.itemId, - itemType: mockPayload.itemType, - sharedWith: mockPayload.sharedWith, - notifyUser: mockPayload.notifyUser, - notificationMessage: mockPayload.notificationMessage, - encryptionKey: encryptionKey, - encryptionAlgorithm: mockPayload.encryptionAlgorithm, - roleId: mockPayload.roleId, - persistPreviousSharing: true, - }), - ); - }); - - it('shareItemWithUser encrypts without kyber for an new user', async () => { - const keys = await generateNewKeys(); - const mockPayload: ShareFileWithUserPayload = { - publicKey: '', - isNewUser: true, - itemId: 'mock-itemId', - itemType: 'file', - notifyUser: false, - notificationMessage: 'mock-notificationMessage', - sharedWith: 'mock-sharedWith', - encryptionAlgorithm: 'mock-encryptionAlgorithm', - roleId: 'mock-roleId', - }; - - const mockUser: Partial = { - mnemonic: - 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - }; - - const mockRootState: Partial = { - user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, - }; - - const user = mockUser as UserSettings; - vi.spyOn(navigationService, 'push').mockImplementation(() => {}); - - const mockShareService = { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }; - - vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( - mockShareService.getSharedFolderInvitationsAsInvitedUser, - ); - vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); - vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - - vi.spyOn(userService, 'preCreateUser').mockReturnValue( - Promise.resolve({ - publicKey: keys.publicKeyArmored, - user: { - uuid: user.userId, - email: user.email, - }, - }), - ); - - const getStateMock = vi.fn(() => mockRootState as RootState); - const dispatchMock = vi.fn(); - - const thunk = shareItemWithUser(mockPayload); - await thunk(dispatchMock, getStateMock, undefined); - - const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; - expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); - - const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ - encryptedMessageInBase64: encryptionKey, - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: '', - }); - - expect(decryptedMessage).toEqual(mockUser.mnemonic); - expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( - expect.objectContaining({ - itemId: mockPayload.itemId, - itemType: mockPayload.itemType, - sharedWith: mockPayload.sharedWith, - notifyUser: mockPayload.notifyUser, - notificationMessage: mockPayload.notificationMessage, - encryptionKey: encryptionKey, - encryptionAlgorithm: mockPayload.encryptionAlgorithm, - roleId: mockPayload.roleId, - persistPreviousSharing: true, - }), - ); - }); - - it('shareItemWithUser encrypts with kyber, keys obtained via getPublicKeyByEmail ', async () => { - const keys = await generateNewKeys(); - const mockPayload: ShareFileWithUserPayload = { - publicKey: '', - publicKyberKey: '', - isNewUser: false, - itemId: 'mock-itemId', - itemType: 'file', - notifyUser: false, - notificationMessage: 'mock-notificationMessage', - sharedWith: 'mock-sharedWith', - encryptionAlgorithm: 'mock-encryptionAlgorithm', - roleId: 'mock-roleId', - }; - - const mockUser: Partial = { - mnemonic: - 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - }; - - const mockRootState: Partial = { - user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, - }; - - vi.spyOn(navigationService, 'push').mockImplementation(() => {}); - - const mockShareService = { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }; - - vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( - mockShareService.getSharedFolderInvitationsAsInvitedUser, - ); - vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); - vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - - vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue( - Promise.resolve({ publicKey: keys.publicKeyArmored, publicKyberKey: keys.publicKyberKeyBase64 }), - ); - - const getStateMock = vi.fn(() => mockRootState as RootState); - const dispatchMock = vi.fn(); - - const thunk = shareItemWithUser(mockPayload); - await thunk(dispatchMock, getStateMock, undefined); - - const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; - expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); - - const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ - encryptedMessageInBase64: encryptionKey, - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: keys.privateKyberKeyBase64, - }); - - expect(decryptedMessage).toEqual(mockUser.mnemonic); - expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( - expect.objectContaining({ - itemId: mockPayload.itemId, - itemType: mockPayload.itemType, - sharedWith: mockPayload.sharedWith, - notifyUser: mockPayload.notifyUser, - notificationMessage: mockPayload.notificationMessage, - encryptionKey: encryptionKey, - encryptionAlgorithm: mockPayload.encryptionAlgorithm, - roleId: mockPayload.roleId, - persistPreviousSharing: true, - }), - ); - }); - - it('shareItemWithUser encrypts without kyber, keys obtained via getPublicKeyByEmail ', async () => { - const keys = await generateNewKeys(); - const mockPayload: ShareFileWithUserPayload = { - publicKey: '', - isNewUser: false, - itemId: 'mock-itemId', - itemType: 'file', - notifyUser: false, - notificationMessage: 'mock-notificationMessage', - sharedWith: 'mock-sharedWith', - encryptionAlgorithm: 'mock-encryptionAlgorithm', - roleId: 'mock-roleId', - }; - - const mockUser: Partial = { - mnemonic: - 'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state', - }; - - const mockRootState: Partial = { - user: { user: mockUser as UserSettings, isInitializing: false, isAuthenticated: false, isInitialized: false }, - }; - - vi.spyOn(navigationService, 'push').mockImplementation(() => {}); - - const mockShareService = { - inviteUserToSharedFolder: vi.fn(), - getSharedFolderInvitationsAsInvitedUser: vi.fn(), - getSharingRoles: vi.fn(), - }; - - vi.spyOn(shareService, 'getSharedFolderInvitationsAsInvitedUser').mockImplementation( - mockShareService.getSharedFolderInvitationsAsInvitedUser, - ); - vi.spyOn(shareService, 'getSharingRoles').mockImplementation(mockShareService.getSharingRoles); - vi.spyOn(shareService, 'inviteUserToSharedFolder').mockImplementation(mockShareService.inviteUserToSharedFolder); - - vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(Promise.resolve({ publicKey: keys.publicKeyArmored })); - - const getStateMock = vi.fn(() => mockRootState as RootState); - const dispatchMock = vi.fn(); - - const thunk = shareItemWithUser(mockPayload); - await thunk(dispatchMock, getStateMock, undefined); - - const [inviteUserToSharedFolderInput] = mockShareService.inviteUserToSharedFolder.mock.calls[0]; - expect(inviteUserToSharedFolderInput.encryptionKey).toBeDefined(); - - const { encryptionKey = '' } = inviteUserToSharedFolderInput; - const decryptedMessage = await hybridDecryptMessageWithPrivateKey({ - encryptedMessageInBase64: encryptionKey, - privateKeyInBase64: Buffer.from(keys.privateKeyArmored).toString('base64'), - privateKyberKeyInBase64: '', - }); - - expect(decryptedMessage).toEqual(mockUser.mnemonic); - expect(mockShareService.inviteUserToSharedFolder).toHaveBeenCalledWith( - expect.objectContaining({ - itemId: mockPayload.itemId, - itemType: mockPayload.itemType, - sharedWith: mockPayload.sharedWith, - notifyUser: mockPayload.notifyUser, - notificationMessage: mockPayload.notificationMessage, - encryptionKey: encryptionKey, - encryptionAlgorithm: mockPayload.encryptionAlgorithm, - roleId: mockPayload.roleId, - persistPreviousSharing: true, - }), - ); - }); -}); diff --git a/src/app/store/slices/sharedLinks/index.ts b/src/app/store/slices/sharedLinks/index.ts index 36cde56dc..bd7142f57 100644 --- a/src/app/store/slices/sharedLinks/index.ts +++ b/src/app/store/slices/sharedLinks/index.ts @@ -1,5 +1,9 @@ import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import shareService from 'app/share/services/share.service'; +import shareService, { + getSharedFolderInvitationsAsInvitedUser, + getSharingRoles, + inviteUserToSharedFolder, +} from 'app/share/services/share.service'; import { RootState } from '../..'; import { Role, SharedFoldersInvitationsAsInvitedUserResponse } from '@internxt/sdk/dist/drive/share/types'; @@ -10,7 +14,7 @@ import notificationsService, { ToastType } from 'app/notifications/services/noti import { UserRoles } from 'app/share/types'; import { t } from 'i18next'; import userService from '../../../auth/services/user.service'; -import { hybridEncryptMessageWithPublicKey } from '../../../crypto/services/pgp.service'; +import { encryptMessageWithPublicKey } from '../../../crypto/services/pgp.service'; export interface ShareLinksState { isLoadingRoles: boolean; @@ -37,7 +41,6 @@ export interface ShareFileWithUserPayload { encryptionAlgorithm: string; roleId: string; publicKey?: string; - publicKyberKey?: string; isNewUser?: boolean; } @@ -54,27 +57,25 @@ const shareItemWithUser = createAsyncThunk => { try { - const newRoles = await shareService.getSharingRoles(); + const newRoles = await getSharingRoles(); if (newRoles.length > 0) { dispatch(sharedActions.setSharedFolderUserRoles(newRoles)); @@ -188,7 +189,7 @@ const getPendingInvitations = createAsyncThunk => { try { - const pendingInvitations = await shareService.getSharedFolderInvitationsAsInvitedUser({}); + const pendingInvitations = await getSharedFolderInvitationsAsInvitedUser({}); dispatch(sharedActions.setPendingInvitations(pendingInvitations.invites)); } catch (err: unknown) {