mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-15 22:38:47 +00:00
파일 생성 시각 및 파일 마지막 수정 시각을 저장하도록 변경
파일 마지막 수정 시각은 반드시 지정되어야 하며, 파일 시스템에서 읽어옵니다. 파일 생성 시각은 선택적으로 지정될 수 있으며, 이미지일 경우 EXIF에서 추출을 시도합니다. 두 값 모두 클라이언트에서 암호화되어 서버에 저장됩니다.
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.46.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.5",
|
"eslint-plugin-tailwindcss": "^3.17.5",
|
||||||
|
"exifreader": "^4.26.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"heic2any": "^0.0.4",
|
"heic2any": "^0.0.4",
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -78,6 +78,9 @@ importers:
|
|||||||
eslint-plugin-tailwindcss:
|
eslint-plugin-tailwindcss:
|
||||||
specifier: ^3.17.5
|
specifier: ^3.17.5
|
||||||
version: 3.17.5(tailwindcss@3.4.17)
|
version: 3.17.5(tailwindcss@3.4.17)
|
||||||
|
exifreader:
|
||||||
|
specifier: ^4.26.0
|
||||||
|
version: 4.26.0
|
||||||
file-saver:
|
file-saver:
|
||||||
specifier: ^2.0.5
|
specifier: ^2.0.5
|
||||||
version: 2.0.5
|
version: 2.0.5
|
||||||
@@ -907,6 +910,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==}
|
resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.9.6':
|
||||||
|
resolution: {integrity: sha512-Su4xcxR0CPGwlDHNmVP09fqET9YxbyDXHaSob6JlBH7L6reTYaeim6zbk9o08UarO0L5GTRo3uzl0D+9lSxmvw==}
|
||||||
|
engines: {node: '>=14.6'}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1343,6 +1350,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
exifreader@4.26.0:
|
||||||
|
resolution: {integrity: sha512-nNN9B0oaXTOpArdnIdJBAro2Sa620m7wMjMA5Xy1rcua0EYHVjzGKM5syBOWDqIG2Qay6Pes/5FOdj65hvZ9Vw==}
|
||||||
|
|
||||||
expand-template@2.0.3:
|
expand-template@2.0.3:
|
||||||
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2852,6 +2862,9 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.19.1
|
'@typescript-eslint/types': 8.19.1
|
||||||
eslint-visitor-keys: 4.2.0
|
eslint-visitor-keys: 4.2.0
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.9.6':
|
||||||
|
optional: true
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.14.0):
|
acorn-jsx@5.3.2(acorn@8.14.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.0
|
acorn: 8.14.0
|
||||||
@@ -3275,6 +3288,10 @@ snapshots:
|
|||||||
|
|
||||||
esutils@2.0.3: {}
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
|
exifreader@4.26.0:
|
||||||
|
optionalDependencies:
|
||||||
|
'@xmldom/xmldom': 0.9.6
|
||||||
|
|
||||||
expand-template@2.0.3: {}
|
expand-template@2.0.3: {}
|
||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ export const getDirectoryInfo = (directoryId: "root" | number, masterKey: Crypto
|
|||||||
return info;
|
return info;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
|
||||||
|
return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
|
||||||
|
};
|
||||||
|
|
||||||
const fetchFileInfo = async (
|
const fetchFileInfo = async (
|
||||||
fileId: number,
|
fileId: number,
|
||||||
masterKey: CryptoKey,
|
masterKey: CryptoKey,
|
||||||
@@ -70,6 +74,11 @@ const fetchFileInfo = async (
|
|||||||
contentType: metadata.contentType,
|
contentType: metadata.contentType,
|
||||||
contentIv: metadata.contentIv,
|
contentIv: metadata.contentIv,
|
||||||
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
||||||
|
createdAt:
|
||||||
|
metadata.createdAt && metadata.createdAtIv
|
||||||
|
? await decryptDate(metadata.createdAt, metadata.createdAtIv, dataKey)
|
||||||
|
: undefined,
|
||||||
|
lastModifiedAt: await decryptDate(metadata.lastModifiedAt, metadata.lastModifiedAtIv, dataKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
infoStore.update(() => newInfo);
|
infoStore.update(() => newInfo);
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export interface NewFileParams {
|
|||||||
encContentIv: string;
|
encContentIv: string;
|
||||||
encName: string;
|
encName: string;
|
||||||
encNameIv: string;
|
encNameIv: string;
|
||||||
|
encCreatedAt: string | null;
|
||||||
|
encCreatedAtIv: string | null;
|
||||||
|
encLastModifiedAt: string;
|
||||||
|
encLastModifiedAtIv: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerDirectory = async (params: NewDirectoryParams) => {
|
export const registerDirectory = async (params: NewDirectoryParams) => {
|
||||||
@@ -154,7 +158,12 @@ export const unregisterDirectory = async (userId: number, directoryId: number) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const registerFile = async (params: NewFileParams) => {
|
export const registerFile = async (params: NewFileParams) => {
|
||||||
if ((params.hskVersion && !params.contentHmac) || (!params.hskVersion && params.contentHmac)) {
|
if (
|
||||||
|
(params.hskVersion && !params.contentHmac) ||
|
||||||
|
(!params.hskVersion && params.contentHmac) ||
|
||||||
|
(params.encCreatedAt && !params.encCreatedAtIv) ||
|
||||||
|
(!params.encCreatedAt && params.encCreatedAtIv)
|
||||||
|
) {
|
||||||
throw new Error("Invalid arguments");
|
throw new Error("Invalid arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +203,14 @@ export const registerFile = async (params: NewFileParams) => {
|
|||||||
dekVersion: params.dekVersion,
|
dekVersion: params.dekVersion,
|
||||||
encContentIv: params.encContentIv,
|
encContentIv: params.encContentIv,
|
||||||
encName: { ciphertext: params.encName, iv: params.encNameIv },
|
encName: { ciphertext: params.encName, iv: params.encNameIv },
|
||||||
|
encCreatedAt:
|
||||||
|
params.encCreatedAt && params.encCreatedAtIv
|
||||||
|
? { ciphertext: params.encCreatedAt, iv: params.encCreatedAtIv }
|
||||||
|
: null,
|
||||||
|
encLastModifiedAt: {
|
||||||
|
ciphertext: params.encLastModifiedAt,
|
||||||
|
iv: params.encLastModifiedAtIv,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.returning({ id: file.id });
|
.returning({ id: file.id });
|
||||||
const { id: fileId } = newFiles[0]!;
|
const { id: fileId } = newFiles[0]!;
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export const file = sqliteTable(
|
|||||||
contentType: text("content_type").notNull(),
|
contentType: text("content_type").notNull(),
|
||||||
encContentIv: text("encrypted_content_iv").notNull(), // Base64
|
encContentIv: text("encrypted_content_iv").notNull(), // Base64
|
||||||
encName: ciphertext("encrypted_name").notNull(),
|
encName: ciphertext("encrypted_name").notNull(),
|
||||||
|
encCreatedAt: ciphertext("encrypted_created_at"),
|
||||||
|
encLastModifiedAt: ciphertext("encrypted_last_modified_at").notNull(),
|
||||||
},
|
},
|
||||||
(t) => ({
|
(t) => ({
|
||||||
ref1: foreignKey({
|
ref1: foreignKey({
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export const fileInfoResponse = z.object({
|
|||||||
contentIv: z.string().base64().nonempty(),
|
contentIv: z.string().base64().nonempty(),
|
||||||
name: z.string().base64().nonempty(),
|
name: z.string().base64().nonempty(),
|
||||||
nameIv: z.string().base64().nonempty(),
|
nameIv: z.string().base64().nonempty(),
|
||||||
|
createdAt: z.string().base64().nonempty().optional(),
|
||||||
|
createdAtIv: z.string().base64().nonempty().optional(),
|
||||||
|
lastModifiedAt: z.string().base64().nonempty(),
|
||||||
|
lastModifiedAtIv: z.string().base64().nonempty(),
|
||||||
});
|
});
|
||||||
export type FileInfoResponse = z.infer<typeof fileInfoResponse>;
|
export type FileInfoResponse = z.infer<typeof fileInfoResponse>;
|
||||||
|
|
||||||
@@ -47,5 +51,9 @@ export const fileUploadRequest = z.object({
|
|||||||
contentIv: z.string().base64().nonempty(),
|
contentIv: z.string().base64().nonempty(),
|
||||||
name: z.string().base64().nonempty(),
|
name: z.string().base64().nonempty(),
|
||||||
nameIv: z.string().base64().nonempty(),
|
nameIv: z.string().base64().nonempty(),
|
||||||
|
createdAt: z.string().base64().nonempty().optional(),
|
||||||
|
createdAtIv: z.string().base64().nonempty().optional(),
|
||||||
|
lastModifiedAt: z.string().base64().nonempty(),
|
||||||
|
lastModifiedAtIv: z.string().base64().nonempty(),
|
||||||
});
|
});
|
||||||
export type FileUploadRequest = z.infer<typeof fileUploadRequest>;
|
export type FileUploadRequest = z.infer<typeof fileUploadRequest>;
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export const getFileInformation = async (userId: number, fileId: number) => {
|
|||||||
contentType: file.contentType,
|
contentType: file.contentType,
|
||||||
encContentIv: file.encContentIv,
|
encContentIv: file.encContentIv,
|
||||||
encName: file.encName,
|
encName: file.encName,
|
||||||
|
encCreatedAt: file.encCreatedAt,
|
||||||
|
encLastModifiedAt: file.encLastModifiedAt,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ export interface FileInfo {
|
|||||||
contentType: string;
|
contentType: string;
|
||||||
contentIv: string;
|
contentIv: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
createdAt?: Date;
|
||||||
|
lastModifiedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const directoryInfoStore = new Map<"root" | number, Writable<DirectoryInfo | null>>();
|
export const directoryInfoStore = new Map<"root" | number, Writable<DirectoryInfo | null>>();
|
||||||
|
|
||||||
export const fileInfoStore = new Map<number, Writable<FileInfo | null>>();
|
export const fileInfoStore = new Map<number, Writable<FileInfo | null>>();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { FileInfo } from "$lib/stores";
|
import type { FileInfo } from "$lib/stores";
|
||||||
|
import { formatDate } from "./service";
|
||||||
import type { SelectedDirectoryEntry } from "../service";
|
import type { SelectedDirectoryEntry } from "../service";
|
||||||
|
|
||||||
import IconDraft from "~icons/material-symbols/draft";
|
import IconDraft from "~icons/material-symbols/draft";
|
||||||
@@ -34,14 +35,17 @@
|
|||||||
{#if $info}
|
{#if $info}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div id="button" onclick={openFile} class="h-12 rounded-xl">
|
<div id="button" onclick={openFile} class="h-14 rounded-xl">
|
||||||
<div id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
|
<div id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
|
||||||
<div class="flex-shrink-0 text-lg">
|
<div class="flex-shrink-0 text-lg">
|
||||||
<IconDraft class="text-blue-400" />
|
<IconDraft class="text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<p title={$info.name} class="flex-grow truncate font-medium">
|
<div class="flex flex-grow flex-col overflow-hidden">
|
||||||
{$info.name}
|
<p title={$info.name} class="truncate font-medium">
|
||||||
</p>
|
{$info.name}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-800">{formatDate($info.createdAt ?? $info.lastModifiedAt)}</p>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
id="open-menu"
|
id="open-menu"
|
||||||
onclick={openMenu}
|
onclick={openMenu}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
{#if $info}
|
{#if $info}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div id="button" onclick={openDirectory} class="h-12 rounded-xl">
|
<div id="button" onclick={openDirectory} class="h-14 rounded-xl">
|
||||||
<div id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
|
<div id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
|
||||||
<div class="flex-shrink-0 text-lg">
|
<div class="flex-shrink-0 text-lg">
|
||||||
<IconFolder />
|
<IconFolder />
|
||||||
|
|||||||
@@ -28,3 +28,15 @@ export const sortEntries = <T extends DirectoryInfo | FileInfo>(
|
|||||||
|
|
||||||
entries.sort((a, b) => sortFunc(get(a), get(b)));
|
entries.sort((a, b) => sortFunc(get(a), get(b)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pad2 = (num: number) => num.toString().padStart(2, "0");
|
||||||
|
|
||||||
|
export const formatDate = (date: Date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
const hours = date.getHours();
|
||||||
|
const minutes = date.getMinutes();
|
||||||
|
|
||||||
|
return `${year}. ${month}. ${day}. ${pad2(hours)}:${pad2(minutes)}`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ExifReader from "exifreader";
|
||||||
import { callGetApi, callPostApi } from "$lib/hooks";
|
import { callGetApi, callPostApi } from "$lib/hooks";
|
||||||
import { storeHmacSecrets } from "$lib/indexedDB";
|
import { storeHmacSecrets } from "$lib/indexedDB";
|
||||||
import {
|
import {
|
||||||
@@ -82,6 +83,32 @@ export const requestDuplicateFileScan = async (file: File, hmacSecret: HmacSecre
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const extractExifDateTime = (fileBuffer: ArrayBuffer) => {
|
||||||
|
const exif = ExifReader.load(fileBuffer);
|
||||||
|
const dateTimeOriginal = exif["DateTimeOriginal"]?.description;
|
||||||
|
const offsetTimeOriginal = exif["OffsetTimeOriginal"]?.description;
|
||||||
|
if (!dateTimeOriginal) return undefined;
|
||||||
|
|
||||||
|
const [date, time] = dateTimeOriginal.split(" ");
|
||||||
|
if (!date || !time) return undefined;
|
||||||
|
|
||||||
|
const [year, month, day] = date.split(":").map(Number);
|
||||||
|
const [hour, minute, second] = time.split(":").map(Number);
|
||||||
|
if (!year || !month || !day || !hour || !minute || !second) return undefined;
|
||||||
|
|
||||||
|
if (!offsetTimeOriginal) {
|
||||||
|
// No timezone information -> Local timezone
|
||||||
|
return new Date(year, month - 1, day, hour, minute, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetSign = offsetTimeOriginal[0] === "+" ? 1 : -1;
|
||||||
|
const [offsetHour, offsetMinute] = offsetTimeOriginal.slice(1).split(":").map(Number);
|
||||||
|
|
||||||
|
const utcDate = Date.UTC(year, month - 1, day, hour, minute, second);
|
||||||
|
const offsetMs = offsetSign * ((offsetHour ?? 0) * 60 + (offsetMinute ?? 0)) * 60 * 1000;
|
||||||
|
return new Date(utcDate - offsetMs);
|
||||||
|
};
|
||||||
|
|
||||||
export const requestFileUpload = async (
|
export const requestFileUpload = async (
|
||||||
file: File,
|
file: File,
|
||||||
fileBuffer: ArrayBuffer,
|
fileBuffer: ArrayBuffer,
|
||||||
@@ -90,9 +117,17 @@ export const requestFileUpload = async (
|
|||||||
masterKey: MasterKey,
|
masterKey: MasterKey,
|
||||||
hmacSecret: HmacSecret,
|
hmacSecret: HmacSecret,
|
||||||
) => {
|
) => {
|
||||||
|
let createdAt = undefined;
|
||||||
|
if (file.type.startsWith("image/")) {
|
||||||
|
createdAt = extractExifDateTime(fileBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
const { dataKey, dataKeyVersion } = await generateDataKey();
|
const { dataKey, dataKeyVersion } = await generateDataKey();
|
||||||
const nameEncrypted = await encryptString(file.name, dataKey);
|
|
||||||
const fileEncrypted = await encryptData(fileBuffer, dataKey);
|
const fileEncrypted = await encryptData(fileBuffer, dataKey);
|
||||||
|
const nameEncrypted = await encryptString(file.name, dataKey);
|
||||||
|
const createdAtEncrypted =
|
||||||
|
createdAt && (await encryptString(createdAt.getTime().toString(), dataKey));
|
||||||
|
const lastModifiedAtEncrypted = await encryptString(file.lastModified.toString(), dataKey);
|
||||||
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.set(
|
form.set(
|
||||||
@@ -108,6 +143,10 @@ export const requestFileUpload = async (
|
|||||||
contentIv: fileEncrypted.iv,
|
contentIv: fileEncrypted.iv,
|
||||||
name: nameEncrypted.ciphertext,
|
name: nameEncrypted.ciphertext,
|
||||||
nameIv: nameEncrypted.iv,
|
nameIv: nameEncrypted.iv,
|
||||||
|
createdAt: createdAtEncrypted?.ciphertext,
|
||||||
|
createdAtIv: createdAtEncrypted?.iv,
|
||||||
|
lastModifiedAt: lastModifiedAtEncrypted.ciphertext,
|
||||||
|
lastModifiedAtIv: lastModifiedAtEncrypted.iv,
|
||||||
} satisfies FileUploadRequest),
|
} satisfies FileUploadRequest),
|
||||||
);
|
);
|
||||||
form.set("content", new Blob([fileEncrypted.ciphertext]));
|
form.set("content", new Blob([fileEncrypted.ciphertext]));
|
||||||
|
|||||||
@@ -16,8 +16,16 @@ export const GET: RequestHandler = async ({ locals, params }) => {
|
|||||||
if (!zodRes.success) error(400, "Invalid path parameters");
|
if (!zodRes.success) error(400, "Invalid path parameters");
|
||||||
const { id } = zodRes.data;
|
const { id } = zodRes.data;
|
||||||
|
|
||||||
const { mekVersion, encDek, dekVersion, contentType, encContentIv, encName } =
|
const {
|
||||||
await getFileInformation(userId, id);
|
mekVersion,
|
||||||
|
encDek,
|
||||||
|
dekVersion,
|
||||||
|
contentType,
|
||||||
|
encContentIv,
|
||||||
|
encName,
|
||||||
|
encCreatedAt,
|
||||||
|
encLastModifiedAt,
|
||||||
|
} = await getFileInformation(userId, id);
|
||||||
return json(
|
return json(
|
||||||
fileInfoResponse.parse({
|
fileInfoResponse.parse({
|
||||||
mekVersion,
|
mekVersion,
|
||||||
@@ -27,6 +35,10 @@ export const GET: RequestHandler = async ({ locals, params }) => {
|
|||||||
contentIv: encContentIv,
|
contentIv: encContentIv,
|
||||||
name: encName.ciphertext,
|
name: encName.ciphertext,
|
||||||
nameIv: encName.iv,
|
nameIv: encName.iv,
|
||||||
|
createdAt: encCreatedAt?.ciphertext,
|
||||||
|
createdAtIv: encCreatedAt?.iv,
|
||||||
|
lastModifiedAt: encLastModifiedAt.ciphertext,
|
||||||
|
lastModifiedAtIv: encLastModifiedAt.iv,
|
||||||
} satisfies FileInfoResponse),
|
} satisfies FileInfoResponse),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,13 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|||||||
contentIv,
|
contentIv,
|
||||||
name,
|
name,
|
||||||
nameIv,
|
nameIv,
|
||||||
|
createdAt,
|
||||||
|
createdAtIv,
|
||||||
|
lastModifiedAt,
|
||||||
|
lastModifiedAtIv,
|
||||||
} = zodRes.data;
|
} = zodRes.data;
|
||||||
|
if ((createdAt && !createdAtIv) || (!createdAt && createdAtIv))
|
||||||
|
error(400, "Invalid request body");
|
||||||
|
|
||||||
await uploadFile(
|
await uploadFile(
|
||||||
{
|
{
|
||||||
@@ -42,6 +48,10 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|||||||
encContentIv: contentIv,
|
encContentIv: contentIv,
|
||||||
encName: name,
|
encName: name,
|
||||||
encNameIv: nameIv,
|
encNameIv: nameIv,
|
||||||
|
encCreatedAt: createdAt ?? null,
|
||||||
|
encCreatedAtIv: createdAtIv ?? null,
|
||||||
|
encLastModifiedAt: lastModifiedAt,
|
||||||
|
encLastModifiedAtIv: lastModifiedAtIv,
|
||||||
},
|
},
|
||||||
content.stream(),
|
content.stream(),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user