Skip to content

Commit

Permalink
feat: update
Browse files Browse the repository at this point in the history
  • Loading branch information
TzuHanLiang committed Dec 25, 2024
1 parent b24d837 commit 94dd851
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 40 deletions.
8 changes: 4 additions & 4 deletions src/components/certificate/certificate_edit_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ const CertificateEditModal: React.FC<CertificateEditModalProps> = ({
);

const isFormValid = useCallback(() => {
const { no, date: formDate, priceBeforeTax, totalPrice, counterParty } = formState;
const { date: formDate, priceBeforeTax, totalPrice, counterParty } = formState;
return (
no &&
no.trim() !== '' &&
// no &&
// no.trim() !== '' &&
formDate &&
formDate > 0 &&
priceBeforeTax &&
Expand Down Expand Up @@ -265,7 +265,7 @@ const CertificateEditModal: React.FC<CertificateEditModalProps> = ({
<div id="price" className="absolute -top-20"></div>
<p className="text-sm font-semibold text-input-text-primary">
{t('certificate:EDIT.INVOICE_NUMBER')}
<span className="text-text-state-error">*</span>
{/* <span className="text-text-state-error">*</span> */}
</p>
<div className="flex w-full items-center">
<input
Expand Down
37 changes: 34 additions & 3 deletions src/components/invoice_upload.tsx/invoice_upload.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useModalContext } from '@/contexts/modal_context';
import APIHandler from '@/lib/utils/api_handler';
Expand All @@ -14,6 +14,7 @@ import { ToastId } from '@/constants/toast_id';
import { useUserCtx } from '@/contexts/user_context';
import { FREE_COMPANY_ID } from '@/constants/config';
import { compressImageToTargetSize } from '@/lib/utils/image_compress';
import { encryptFileWithPublicKey, importPublicKey } from '@/lib/utils/crypto';

interface InvoiceUploadProps {
isDisabled: boolean;
Expand All @@ -32,8 +33,10 @@ const InvoiceUpload: React.FC<InvoiceUploadProps> = ({
const { selectedCompany } = useUserCtx();
const { toastHandler, messageModalDataHandler, messageModalVisibilityHandler } =
useModalContext();
const [publicKey, setPublicKey] = useState<CryptoKey | null>(null);
const { trigger: uploadFileAPI } = APIHandler<IFileUIBeta>(APIName.FILE_UPLOAD);
const { trigger: createCertificateAPI } = APIHandler<ICertificate>(APIName.CERTIFICATE_POST_V2);
const { trigger: fetchPublicKey } = APIHandler<JsonWebKey>(APIName.PUBLIC_KEY_GET);

const handleUploadCancelled = useCallback(() => {
setFiles([]);
Expand Down Expand Up @@ -75,11 +78,39 @@ const InvoiceUpload: React.FC<InvoiceUploadProps> = ({
]
);

const encryptFileWithKey = async (file: File) => {
try {
let key = publicKey;
if (!key) {
const { success, data } = await fetchPublicKey({
params: { companyId: selectedCompany?.id ?? FREE_COMPANY_ID },
});
if (!success || !data) {
throw new Error(t('certificate:UPLOAD.FAILED'));
}
const cryptokey = await importPublicKey(data);
setPublicKey(cryptokey);
key = cryptokey;
}
const { encryptedFile, iv, encryptedSymmetricKey } = await encryptFileWithPublicKey(
file,
key
);
const formData = new FormData();
formData.append('file', encryptedFile);
formData.append('encryptedSymmetricKey', encryptedSymmetricKey);
formData.append('publicKey', JSON.stringify(key));
formData.append('iv', Array.from(iv).join(','));
return formData;
} catch (error) {
throw new Error(t('certificate:ERROR.ENCRYPT_FILE'));
}
};

const handleUpload = useCallback(
async (file: File) => {
try {
const formData = new FormData();
formData.append('file', file);
const formData = await encryptFileWithKey(file);
const targetSize = 1 * 1024 * 1024; // Info: (20241206 - tzuhan) 1MB
const maxSize = 4 * 1024 * 1024;
if (file.size > maxSize) {
Expand Down
16 changes: 16 additions & 0 deletions src/lib/utils/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@/constants/crypto';
import { promises as fs } from 'fs';
import path from 'path';
import { IV_LENGTH } from '@/constants/config';

/* Info: (20240822 - Shirley)
- 實作混合加密 (hybrid encryption),用對稱加密密鑰將檔案加密,用非對稱加密的 public key 加密對稱密鑰,用非對稱加密的 private key 解密對稱密鑰
Expand Down Expand Up @@ -331,3 +332,18 @@ export function arrayBufferToBuffer(arrayBuffer: ArrayBuffer): Buffer {
}
return buffer;
}

export const encryptFileWithPublicKey = async (file: File, publicKey: CryptoKey) => {
const arrayBuffer = await file.arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const { encryptedContent, encryptedSymmetricKey } = await encryptFile(arrayBuffer, publicKey, iv);
const encryptedFile = new File([encryptedContent], file.name, {
type: file.type,
});

return {
encryptedFile,
iv,
encryptedSymmetricKey,
};
};
60 changes: 27 additions & 33 deletions src/pages/mobile_upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import { MessageType } from '@/interfaces/message_modal';
import { PiHouse } from 'react-icons/pi';
import { ToastId } from '@/constants/toast_id';
import { ToastType } from '@/interfaces/toastify';
import { encryptFile, generateKeyPair } from '@/lib/utils/crypto';
import { IV_LENGTH } from '@/constants/config';
import { encryptFileWithPublicKey, importPublicKey } from '@/lib/utils/crypto';
import { compressImageToTargetSize } from '@/lib/utils/image_compress';
import { RxCross1 } from 'react-icons/rx';

Expand All @@ -44,6 +43,8 @@ const MobileUploadPage: React.FC = () => {
const { trigger: uploadFileAPI } = APIHandler<number>(APIName.FILE_UPLOAD);
const { messageModalDataHandler, messageModalVisibilityHandler, toastHandler } =
useModalContext();
const [publicKey, setPublicKey] = useState<CryptoKey | null>(null);
const { trigger: fetchPublicKey } = APIHandler<JsonWebKey>(APIName.ROOM_GET_PUBLIC_KEY_BY_ID);

const handleFilesSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
try {
Expand Down Expand Up @@ -91,27 +92,31 @@ const MobileUploadPage: React.FC = () => {
URL.revokeObjectURL(file.url);
};

// Deprecated: (20241206 - tzuhan) For local testing
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const encryptFileWithPublicKey = async (file: File, publicKey: CryptoKey) => {
const encryptFileWithKey = async (file: File) => {
if (!token) throw new Error(t('certificate:ERROR.TOKEN_NOT_PROVIDED'));
try {
const arrayBuffer = await file.arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const { encryptedContent, encryptedSymmetricKey } = await encryptFile(
arrayBuffer,
publicKey,
iv
let key = publicKey;
if (!key) {
const { success, data } = await fetchPublicKey({
params: { roomId: token },
});
if (!success || !data) {
throw new Error(t('certificate:UPLOAD.FAILED'));
}
const cryptokey = await importPublicKey(data);
setPublicKey(cryptokey);
key = cryptokey;
}
const { encryptedFile, iv, encryptedSymmetricKey } = await encryptFileWithPublicKey(
file,
key
);
const encryptedFile = new File([encryptedContent], file.name, {
type: file.type,
});

return {
encryptedFile,
iv,
encryptedSymmetricKey,
};
const formData = new FormData();
formData.append('file', encryptedFile);
formData.append('encryptedSymmetricKey', encryptedSymmetricKey);
formData.append('publicKey', JSON.stringify(key));
formData.append('iv', Array.from(iv).join(','));
return formData;
} catch (error) {
throw new Error(t('certificate:ERROR.ENCRYPT_FILE'));
}
Expand All @@ -129,21 +134,10 @@ const MobileUploadPage: React.FC = () => {
});
return;
}
const keyPair = await generateKeyPair();

await Promise.all(
selectedFiles.map(async (fileUI) => {
const { encryptedFile, iv, encryptedSymmetricKey } = await encryptFileWithPublicKey(
fileUI.file,
keyPair.publicKey
);
const formData = new FormData();
formData.append('file', encryptedFile);
formData.append('encryptedSymmetricKey', encryptedSymmetricKey);

// Info: (20241224 - Murky) Public key 可以提供JsonWebKey格式嗎?
formData.append('publicKey', JSON.stringify(keyPair.publicKey));
formData.append('iv', Array.from(iv).join(','));

const formData = await encryptFileWithKey(fileUI.file);
const { success, data: fileId } = await uploadFileAPI({
query: {
type: UploadType.ROOM,
Expand Down

0 comments on commit 94dd851

Please sign in to comment.