mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 16:16:55 +00:00
Compare commits
8 Commits
b9e6f17b0c
...
v0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3906ec4371 | ||
|
|
90ac5ba4c3 | ||
|
|
dfffa004ac | ||
|
|
0cd55a413d | ||
|
|
361d966a59 | ||
|
|
aef43b8bfa | ||
|
|
7f128cccf6 | ||
|
|
a198e5f6dc |
@@ -89,15 +89,11 @@ export const encryptData = async (data: BufferSource, dataKey: CryptoKey) => {
|
|||||||
return { ciphertext, iv: encodeToBase64(iv.buffer) };
|
return { ciphertext, iv: encodeToBase64(iv.buffer) };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decryptData = async (
|
export const decryptData = async (ciphertext: BufferSource, iv: string, dataKey: CryptoKey) => {
|
||||||
ciphertext: BufferSource,
|
|
||||||
iv: string | BufferSource,
|
|
||||||
dataKey: CryptoKey,
|
|
||||||
) => {
|
|
||||||
return await window.crypto.subtle.decrypt(
|
return await window.crypto.subtle.decrypt(
|
||||||
{
|
{
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
iv: typeof iv === "string" ? decodeFromBase64(iv) : iv,
|
iv: decodeFromBase64(iv),
|
||||||
} satisfies AesGcmParams,
|
} satisfies AesGcmParams,
|
||||||
dataKey,
|
dataKey,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
|
|||||||
@@ -62,14 +62,15 @@ const requestFileDownload = limitFunction(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const decryptFile = limitFunction(
|
const decryptFile = limitFunction(
|
||||||
async (state: FileDownloadState, fileEncrypted: ArrayBuffer, dataKey: CryptoKey) => {
|
async (
|
||||||
|
state: FileDownloadState,
|
||||||
|
fileEncrypted: ArrayBuffer,
|
||||||
|
fileEncryptedIv: string,
|
||||||
|
dataKey: CryptoKey,
|
||||||
|
) => {
|
||||||
state.status = "decrypting";
|
state.status = "decrypting";
|
||||||
|
|
||||||
const fileBuffer = await decryptData(
|
const fileBuffer = await decryptData(fileEncrypted, fileEncryptedIv, dataKey);
|
||||||
fileEncrypted.slice(12),
|
|
||||||
fileEncrypted.slice(0, 12),
|
|
||||||
dataKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
state.status = "decrypted";
|
state.status = "decrypted";
|
||||||
state.result = fileBuffer;
|
state.result = fileBuffer;
|
||||||
@@ -78,7 +79,7 @@ const decryptFile = limitFunction(
|
|||||||
{ concurrency: 4 },
|
{ concurrency: 4 },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const downloadFile = async (id: number, dataKey: CryptoKey) => {
|
export const downloadFile = async (id: number, fileEncryptedIv: string, dataKey: CryptoKey) => {
|
||||||
downloadingFiles.push({
|
downloadingFiles.push({
|
||||||
id,
|
id,
|
||||||
status: "download-pending",
|
status: "download-pending",
|
||||||
@@ -86,7 +87,7 @@ export const downloadFile = async (id: number, dataKey: CryptoKey) => {
|
|||||||
const state = downloadingFiles.at(-1)!;
|
const state = downloadingFiles.at(-1)!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await decryptFile(state, await requestFileDownload(state, id), dataKey);
|
return await decryptFile(state, await requestFileDownload(state, id), fileEncryptedIv, dataKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.status = "error";
|
state.status = "error";
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { decryptData } from "$lib/modules/crypto";
|
|||||||
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
|
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
|
||||||
import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs";
|
import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs";
|
||||||
import { getThumbnailUrl } from "$lib/modules/thumbnail";
|
import { getThumbnailUrl } from "$lib/modules/thumbnail";
|
||||||
|
import { isTRPCClientError, trpc } from "$trpc/client";
|
||||||
|
|
||||||
const loadedThumbnails = new LRUCache<number, Writable<string>>({ max: 100 });
|
const loadedThumbnails = new LRUCache<number, Writable<string>>({ max: 100 });
|
||||||
const loadingThumbnails = new Map<number, Writable<string | undefined>>();
|
const loadingThumbnails = new Map<number, Writable<string | undefined>>();
|
||||||
@@ -17,18 +18,25 @@ const fetchFromOpfs = async (fileId: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchFromServer = async (fileId: number, dataKey: CryptoKey) => {
|
const fetchFromServer = async (fileId: number, dataKey: CryptoKey) => {
|
||||||
const res = await fetch(`/api/file/${fileId}/thumbnail/download`);
|
try {
|
||||||
if (!res.ok) return null;
|
const [thumbnailEncrypted, { contentIv: thumbnailEncryptedIv }] = await Promise.all([
|
||||||
|
fetch(`/api/file/${fileId}/thumbnail/download`),
|
||||||
const thumbnailEncrypted = await res.arrayBuffer();
|
trpc().file.thumbnail.query({ id: fileId }),
|
||||||
|
]);
|
||||||
const thumbnailBuffer = await decryptData(
|
const thumbnailBuffer = await decryptData(
|
||||||
thumbnailEncrypted.slice(12),
|
await thumbnailEncrypted.arrayBuffer(),
|
||||||
thumbnailEncrypted.slice(0, 12),
|
thumbnailEncryptedIv,
|
||||||
dataKey,
|
dataKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
void writeFile(`/thumbnail/file/${fileId}`, thumbnailBuffer);
|
void writeFile(`/thumbnail/file/${fileId}`, thumbnailBuffer);
|
||||||
return getThumbnailUrl(thumbnailBuffer);
|
return getThumbnailUrl(thumbnailBuffer);
|
||||||
|
} catch (e) {
|
||||||
|
if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFileThumbnail = (file: SummarizedFileInfo) => {
|
export const getFileThumbnail = (file: SummarizedFileInfo) => {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const cache = new FilesystemCache<number, MaybeFileInfo>({
|
|||||||
parentId: file.parent,
|
parentId: file.parent,
|
||||||
dataKey: metadata.dataKey,
|
dataKey: metadata.dataKey,
|
||||||
contentType: file.contentType,
|
contentType: file.contentType,
|
||||||
|
contentIv: file.contentIv,
|
||||||
name: metadata.name,
|
name: metadata.name,
|
||||||
createdAt: metadata.createdAt,
|
createdAt: metadata.createdAt,
|
||||||
lastModifiedAt: metadata.lastModifiedAt,
|
lastModifiedAt: metadata.lastModifiedAt,
|
||||||
@@ -117,6 +118,7 @@ const cache = new FilesystemCache<number, MaybeFileInfo>({
|
|||||||
exists: true as const,
|
exists: true as const,
|
||||||
parentId: metadataRaw.parent,
|
parentId: metadataRaw.parent,
|
||||||
contentType: metadataRaw.contentType,
|
contentType: metadataRaw.contentType,
|
||||||
|
contentIv: metadataRaw.contentIv,
|
||||||
categories,
|
categories,
|
||||||
...metadata,
|
...metadata,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface FileInfo {
|
|||||||
parentId: DirectoryId;
|
parentId: DirectoryId;
|
||||||
dataKey?: DataKey;
|
dataKey?: DataKey;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
|
contentIv?: string;
|
||||||
name: string;
|
name: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
lastModifiedAt: Date;
|
lastModifiedAt: Date;
|
||||||
@@ -41,7 +42,7 @@ export type MaybeFileInfo =
|
|||||||
| (FileInfo & { exists: true })
|
| (FileInfo & { exists: true })
|
||||||
| ({ id: number; exists: false } & AllUndefined<Omit<FileInfo, "id">>);
|
| ({ id: number; exists: false } & AllUndefined<Omit<FileInfo, "id">>);
|
||||||
|
|
||||||
export type SummarizedFileInfo = Omit<FileInfo, "categories">;
|
export type SummarizedFileInfo = Omit<FileInfo, "contentIv" | "categories">;
|
||||||
export type CategoryFileInfo = SummarizedFileInfo & { isRecursive: boolean };
|
export type CategoryFileInfo = SummarizedFileInfo & { isRecursive: boolean };
|
||||||
|
|
||||||
interface LocalCategoryInfo {
|
interface LocalCategoryInfo {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
export const parseRangeHeader = (rangeHeader: string | null) => {
|
|
||||||
if (!rangeHeader) return undefined;
|
|
||||||
|
|
||||||
const firstRange = rangeHeader.split(",")[0]!.trim();
|
|
||||||
const parts = firstRange.replace(/bytes=/, "").split("-");
|
|
||||||
return {
|
|
||||||
start: parts[0] ? parseInt(parts[0], 10) : undefined,
|
|
||||||
end: parts[1] ? parseInt(parts[1], 10) : undefined,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getContentRangeHeader = (range?: { start: number; end: number; total: number }) => {
|
|
||||||
return range && { "Content-Range": `bytes ${range.start}-${range.end}/${range.total}` };
|
|
||||||
};
|
|
||||||
@@ -10,69 +10,30 @@ import { FileRepo, MediaRepo, IntegrityError } from "$lib/server/db";
|
|||||||
import env from "$lib/server/loadenv";
|
import env from "$lib/server/loadenv";
|
||||||
import { safeUnlink } from "$lib/server/modules/filesystem";
|
import { safeUnlink } from "$lib/server/modules/filesystem";
|
||||||
|
|
||||||
const createEncContentStream = async (
|
export const getFileStream = async (userId: number, fileId: number) => {
|
||||||
path: string,
|
|
||||||
iv: Buffer,
|
|
||||||
range?: { start?: number; end?: number },
|
|
||||||
) => {
|
|
||||||
const { size: fileSize } = await stat(path);
|
|
||||||
const ivSize = iv.byteLength;
|
|
||||||
const totalSize = fileSize + ivSize;
|
|
||||||
|
|
||||||
const start = range?.start ?? 0;
|
|
||||||
const end = range?.end ?? totalSize - 1;
|
|
||||||
if (start > end || start < 0 || end >= totalSize) {
|
|
||||||
error(416, "Invalid range");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
encContentStream: Readable.toWeb(
|
|
||||||
Readable.from(
|
|
||||||
(async function* () {
|
|
||||||
if (start < ivSize) {
|
|
||||||
yield iv.subarray(start, Math.min(end + 1, ivSize));
|
|
||||||
}
|
|
||||||
if (end >= ivSize) {
|
|
||||||
yield* createReadStream(path, {
|
|
||||||
start: Math.max(0, start - ivSize),
|
|
||||||
end: end - ivSize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
range: { start, end, total: totalSize },
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFileStream = async (
|
|
||||||
userId: number,
|
|
||||||
fileId: number,
|
|
||||||
range?: { start?: number; end?: number },
|
|
||||||
) => {
|
|
||||||
const file = await FileRepo.getFile(userId, fileId);
|
const file = await FileRepo.getFile(userId, fileId);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
error(404, "Invalid file id");
|
error(404, "Invalid file id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return createEncContentStream(file.path, Buffer.from(file.encContentIv, "base64"), range);
|
const { size } = await stat(file.path);
|
||||||
|
return {
|
||||||
|
encContentStream: Readable.toWeb(createReadStream(file.path)),
|
||||||
|
encContentSize: size,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFileThumbnailStream = async (
|
export const getFileThumbnailStream = async (userId: number, fileId: number) => {
|
||||||
userId: number,
|
|
||||||
fileId: number,
|
|
||||||
range?: { start?: number; end?: number },
|
|
||||||
) => {
|
|
||||||
const thumbnail = await MediaRepo.getFileThumbnail(userId, fileId);
|
const thumbnail = await MediaRepo.getFileThumbnail(userId, fileId);
|
||||||
if (!thumbnail) {
|
if (!thumbnail) {
|
||||||
error(404, "File or its thumbnail not found");
|
error(404, "File or its thumbnail not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return createEncContentStream(
|
const { size } = await stat(thumbnail.path);
|
||||||
thumbnail.path,
|
return {
|
||||||
Buffer.from(thumbnail.encContentIv, "base64"),
|
encContentStream: Readable.toWeb(createReadStream(thumbnail.path)),
|
||||||
range,
|
encContentSize: size,
|
||||||
);
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadFileThumbnail = async (
|
export const uploadFileThumbnail = async (
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import {
|
|||||||
import type { FileThumbnailUploadRequest } from "$lib/server/schemas";
|
import type { FileThumbnailUploadRequest } from "$lib/server/schemas";
|
||||||
import { trpc } from "$trpc/client";
|
import { trpc } from "$trpc/client";
|
||||||
|
|
||||||
export const requestFileDownload = async (fileId: number, dataKey: CryptoKey) => {
|
export const requestFileDownload = async (
|
||||||
|
fileId: number,
|
||||||
|
fileEncryptedIv: string,
|
||||||
|
dataKey: CryptoKey,
|
||||||
|
) => {
|
||||||
const cache = await getFileCache(fileId);
|
const cache = await getFileCache(fileId);
|
||||||
if (cache) return cache;
|
if (cache) return cache;
|
||||||
|
|
||||||
const fileBuffer = await downloadFile(fileId, dataKey);
|
const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey);
|
||||||
storeFileCache(fileId, fileBuffer); // Intended
|
storeFileCache(fileId, fileBuffer); // Intended
|
||||||
return fileBuffer;
|
return fileBuffer;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { page } from "$app/state";
|
import { page } from "$app/state";
|
||||||
import { FullscreenDiv } from "$lib/components/atoms";
|
import { FullscreenDiv } from "$lib/components/atoms";
|
||||||
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules";
|
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules";
|
||||||
import { getFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem";
|
import { getFileInfo, type FileInfo, type MaybeFileInfo } from "$lib/modules/filesystem";
|
||||||
import { captureVideoThumbnail } from "$lib/modules/thumbnail";
|
import { captureVideoThumbnail } from "$lib/modules/thumbnail";
|
||||||
import { getFileDownloadState } from "$lib/modules/file";
|
import { getFileDownloadState } from "$lib/modules/file";
|
||||||
import { masterKeyStore } from "$lib/stores";
|
import { masterKeyStore } from "$lib/stores";
|
||||||
@@ -95,12 +95,14 @@
|
|||||||
untrack(() => {
|
untrack(() => {
|
||||||
if (!downloadState && !isDownloadRequested) {
|
if (!downloadState && !isDownloadRequested) {
|
||||||
isDownloadRequested = true;
|
isDownloadRequested = true;
|
||||||
requestFileDownload(data.id, info!.dataKey!.key).then(async (buffer) => {
|
requestFileDownload(data.id, info!.contentIv!, info!.dataKey!.key).then(
|
||||||
|
async (buffer) => {
|
||||||
const blob = await updateViewer(buffer, contentType);
|
const blob = await updateViewer(buffer, contentType);
|
||||||
if (!viewerType) {
|
if (!viewerType) {
|
||||||
FileSaver.saveAs(blob, info!.name);
|
FileSaver.saveAs(blob, info!.name);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -108,9 +110,7 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (info?.exists && downloadState?.status === "decrypted") {
|
if (info?.exists && downloadState?.status === "decrypted") {
|
||||||
untrack(
|
untrack(() => !isDownloadRequested && updateViewer(downloadState.result!, info!.contentIv!));
|
||||||
() => !isDownloadRequested && updateViewer(downloadState.result!, info!.contentType!),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => {
|
|||||||
await scheduler.schedule(
|
await scheduler.schedule(
|
||||||
async () => {
|
async () => {
|
||||||
statuses.set(fileInfo.id, "generation-pending");
|
statuses.set(fileInfo.id, "generation-pending");
|
||||||
file = await requestFileDownload(fileInfo.id, fileInfo.dataKey?.key!);
|
file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey?.key!);
|
||||||
return file.byteLength;
|
return file.byteLength;
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { error } from "@sveltejs/kit";
|
import { error } from "@sveltejs/kit";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { authorize } from "$lib/server/modules/auth";
|
import { authorize } from "$lib/server/modules/auth";
|
||||||
import { parseRangeHeader, getContentRangeHeader } from "$lib/server/modules/http";
|
|
||||||
import { getFileStream } from "$lib/server/services/file";
|
import { getFileStream } from "$lib/server/services/file";
|
||||||
import type { RequestHandler } from "./$types";
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
const downloadHandler = async (
|
export const GET: RequestHandler = async ({ locals, params }) => {
|
||||||
locals: App.Locals,
|
|
||||||
params: Record<string, string>,
|
|
||||||
request: Request,
|
|
||||||
) => {
|
|
||||||
const { userId } = await authorize(locals, "activeClient");
|
const { userId } = await authorize(locals, "activeClient");
|
||||||
|
|
||||||
const zodRes = z
|
const zodRes = z
|
||||||
@@ -20,29 +15,11 @@ const downloadHandler = async (
|
|||||||
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 { encContentStream, range } = await getFileStream(
|
const { encContentStream, encContentSize } = await getFileStream(userId, id);
|
||||||
userId,
|
return new Response(encContentStream as ReadableStream, {
|
||||||
id,
|
|
||||||
parseRangeHeader(request.headers.get("Range")),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
stream: encContentStream,
|
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Ranges": "bytes",
|
|
||||||
"Content-Length": (range.end - range.start + 1).toString(),
|
|
||||||
"Content-Type": "application/octet-stream",
|
"Content-Type": "application/octet-stream",
|
||||||
...getContentRangeHeader(range),
|
"Content-Length": encContentSize.toString(),
|
||||||
},
|
},
|
||||||
isRangeRequest: !!range,
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ locals, params, request }) => {
|
|
||||||
const { stream, headers, isRangeRequest } = await downloadHandler(locals, params, request);
|
|
||||||
return new Response(stream as ReadableStream, { status: isRangeRequest ? 206 : 200, headers });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HEAD: RequestHandler = async ({ locals, params, request }) => {
|
|
||||||
const { headers, isRangeRequest } = await downloadHandler(locals, params, request);
|
|
||||||
return new Response(null, { status: isRangeRequest ? 206 : 200, headers });
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { error } from "@sveltejs/kit";
|
import { error } from "@sveltejs/kit";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { authorize } from "$lib/server/modules/auth";
|
import { authorize } from "$lib/server/modules/auth";
|
||||||
import { parseRangeHeader, getContentRangeHeader } from "$lib/server/modules/http";
|
|
||||||
import { getFileThumbnailStream } from "$lib/server/services/file";
|
import { getFileThumbnailStream } from "$lib/server/services/file";
|
||||||
import type { RequestHandler } from "./$types";
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
const downloadHandler = async (
|
export const GET: RequestHandler = async ({ locals, params }) => {
|
||||||
locals: App.Locals,
|
|
||||||
params: Record<string, string>,
|
|
||||||
request: Request,
|
|
||||||
) => {
|
|
||||||
const { userId } = await authorize(locals, "activeClient");
|
const { userId } = await authorize(locals, "activeClient");
|
||||||
|
|
||||||
const zodRes = z
|
const zodRes = z
|
||||||
@@ -20,29 +15,11 @@ const downloadHandler = async (
|
|||||||
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 { encContentStream, range } = await getFileThumbnailStream(
|
const { encContentStream, encContentSize } = await getFileThumbnailStream(userId, id);
|
||||||
userId,
|
return new Response(encContentStream as ReadableStream, {
|
||||||
id,
|
|
||||||
parseRangeHeader(request.headers.get("Range")),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
stream: encContentStream,
|
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Ranges": "bytes",
|
|
||||||
"Content-Length": (range.end - range.start + 1).toString(),
|
|
||||||
"Content-Type": "application/octet-stream",
|
"Content-Type": "application/octet-stream",
|
||||||
...getContentRangeHeader(range),
|
"Content-Length": encContentSize.toString(),
|
||||||
},
|
},
|
||||||
isRangeRequest: !!range,
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ locals, params, request }) => {
|
|
||||||
const { stream, headers, isRangeRequest } = await downloadHandler(locals, params, request);
|
|
||||||
return new Response(stream as ReadableStream, { status: isRangeRequest ? 206 : 200, headers });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HEAD: RequestHandler = async ({ locals, params, request }) => {
|
|
||||||
const { headers, isRangeRequest } = await downloadHandler(locals, params, request);
|
|
||||||
return new Response(null, { status: isRangeRequest ? 206 : 200, headers });
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const fileRouter = router({
|
|||||||
dek: file.encDek,
|
dek: file.encDek,
|
||||||
dekVersion: file.dekVersion,
|
dekVersion: file.dekVersion,
|
||||||
contentType: file.contentType,
|
contentType: file.contentType,
|
||||||
|
contentIv: file.encContentIv,
|
||||||
name: file.encName.ciphertext,
|
name: file.encName.ciphertext,
|
||||||
nameIv: file.encName.iv,
|
nameIv: file.encName.iv,
|
||||||
createdAt: file.encCreatedAt?.ciphertext,
|
createdAt: file.encCreatedAt?.ciphertext,
|
||||||
@@ -57,6 +58,7 @@ const fileRouter = router({
|
|||||||
dek: file.encDek,
|
dek: file.encDek,
|
||||||
dekVersion: file.dekVersion,
|
dekVersion: file.dekVersion,
|
||||||
contentType: file.contentType,
|
contentType: file.contentType,
|
||||||
|
contentIv: file.encContentIv,
|
||||||
name: file.encName.ciphertext,
|
name: file.encName.ciphertext,
|
||||||
nameIv: file.encName.iv,
|
nameIv: file.encName.iv,
|
||||||
createdAt: file.encCreatedAt?.ciphertext,
|
createdAt: file.encCreatedAt?.ciphertext,
|
||||||
@@ -156,7 +158,7 @@ const fileRouter = router({
|
|||||||
throw new TRPCError({ code: "NOT_FOUND", message: "File or its thumbnail not found" });
|
throw new TRPCError({ code: "NOT_FOUND", message: "File or its thumbnail not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { updatedAt: thumbnail.updatedAt };
|
return { updatedAt: thumbnail.updatedAt, contentIv: thumbnail.encContentIv };
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user