mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-11-04 08:10:28 +09:00
SSR을 비활성화하여 SPA로 전환
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b35585c7bb
commit
35f3e92b5a
34 files changed
+145
-132
No files matched your search
@@ -5,6 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
"dev:remote": "__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.ts.net vite dev --host 0.0.0.0",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LRUCache } from "lru-cache";
|
import { LRUCache } from "lru-cache";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { decryptChunk } from "$lib/modules/crypto";
|
import { decryptChunk } 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";
|
||||||
@@ -27,10 +26,7 @@ const fetchFromServer = async (fileId: number, dataKey: CryptoKey) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getFileThumbnail = (file: SummarizedFileInfo) => {
|
export const getFileThumbnail = (file: SummarizedFileInfo) => {
|
||||||
if (
|
if (!(file.contentType.startsWith("image/") || file.contentType.startsWith("video/"))) {
|
||||||
!browser ||
|
|
||||||
!(file.contentType.startsWith("image/") || file.contentType.startsWith("video/"))
|
|
||||||
) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const requestDuplicateFileScan = limitFunction(
|
|||||||
state.status = "encryption-pending";
|
state.status = "encryption-pending";
|
||||||
|
|
||||||
const fileSigned = encodeToBase64(await signMessageHmac(file, hmacSecret.secret));
|
const fileSigned = encodeToBase64(await signMessageHmac(file, hmacSecret.secret));
|
||||||
const files = await trpc().file.listByHash.query({
|
const files = await trpc.file.listByHash.query({
|
||||||
hskVersion: hmacSecret.version,
|
hskVersion: hmacSecret.version,
|
||||||
contentHmac: fileSigned,
|
contentHmac: fileSigned,
|
||||||
});
|
});
|
||||||
@@ -134,7 +134,7 @@ const requestFileMetadataEncryption = limitFunction(
|
|||||||
generateThumbnail(file).then((blob) => blob?.arrayBuffer()),
|
generateThumbnail(file).then((blob) => blob?.arrayBuffer()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { uploadId } = await trpc().upload.startFileUpload.mutate({
|
const { uploadId } = await trpc.upload.startFileUpload.mutate({
|
||||||
chunks: Math.ceil(file.size / CHUNK_SIZE),
|
chunks: Math.ceil(file.size / CHUNK_SIZE),
|
||||||
parent: fileMetadata.parentId,
|
parent: fileMetadata.parentId,
|
||||||
mekVersion: masterKey.version,
|
mekVersion: masterKey.version,
|
||||||
@@ -175,7 +175,7 @@ const requestFileUpload = limitFunction(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { file: fileId } = await trpc().upload.completeFileUpload.mutate({
|
const { file: fileId } = await trpc.upload.completeFileUpload.mutate({
|
||||||
uploadId,
|
uploadId,
|
||||||
contentHmac: fileSigned,
|
contentHmac: fileSigned,
|
||||||
encContentHash,
|
encContentHash,
|
||||||
@@ -183,14 +183,14 @@ const requestFileUpload = limitFunction(
|
|||||||
|
|
||||||
if (thumbnailBuffer) {
|
if (thumbnailBuffer) {
|
||||||
try {
|
try {
|
||||||
const { uploadId } = await trpc().upload.startFileThumbnailUpload.mutate({
|
const { uploadId } = await trpc.upload.startFileThumbnailUpload.mutate({
|
||||||
file: fileId,
|
file: fileId,
|
||||||
dekVersion: dataKeyVersion,
|
dekVersion: dataKeyVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
await uploadBlob(uploadId, new Blob([thumbnailBuffer]), dataKey);
|
await uploadBlob(uploadId, new Blob([thumbnailBuffer]), dataKey);
|
||||||
|
|
||||||
await trpc().upload.completeFileThumbnailUpload.mutate({ uploadId });
|
await trpc.upload.completeFileThumbnailUpload.mutate({ uploadId });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const cache = new FilesystemCache<CategoryId, MaybeCategoryInfo>({
|
|||||||
|
|
||||||
async fetchFromServer(id, cachedInfo, masterKey) {
|
async fetchFromServer(id, cachedInfo, masterKey) {
|
||||||
try {
|
try {
|
||||||
const category = await trpc().category.get.query({ id, recurse: true });
|
const category = await trpc.category.get.query({ id, recurse: true });
|
||||||
const [subCategories, files, metadata] = await Promise.all([
|
const [subCategories, files, metadata] = await Promise.all([
|
||||||
Promise.all(
|
Promise.all(
|
||||||
category.subCategories.map(async (category) => ({
|
category.subCategories.map(async (category) => ({
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const cache = new FilesystemCache<DirectoryId, MaybeDirectoryInfo>({
|
|||||||
|
|
||||||
async fetchFromServer(id, _cachedInfo, masterKey) {
|
async fetchFromServer(id, _cachedInfo, masterKey) {
|
||||||
try {
|
try {
|
||||||
const directory = await trpc().directory.get.query({ id });
|
const directory = await trpc.directory.get.query({ id });
|
||||||
const [subDirectories, files, metadata] = await Promise.all([
|
const [subDirectories, files, metadata] = await Promise.all([
|
||||||
Promise.all(
|
Promise.all(
|
||||||
directory.subDirectories.map(async (directory) => ({
|
directory.subDirectories.map(async (directory) => ({
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const cache = new FilesystemCache<number, MaybeFileInfo>({
|
|||||||
|
|
||||||
async fetchFromServer(id, _cachedInfo, masterKey) {
|
async fetchFromServer(id, _cachedInfo, masterKey) {
|
||||||
try {
|
try {
|
||||||
const file = await trpc().file.get.query({ id });
|
const file = await trpc.file.get.query({ id });
|
||||||
const [categories, metadata] = await Promise.all([
|
const [categories, metadata] = await Promise.all([
|
||||||
Promise.all(
|
Promise.all(
|
||||||
file.categories.map(async (category) => ({
|
file.categories.map(async (category) => ({
|
||||||
@@ -101,7 +101,7 @@ const cache = new FilesystemCache<number, MaybeFileInfo>({
|
|||||||
async bulkFetchFromServer(ids, masterKey) {
|
async bulkFetchFromServer(ids, masterKey) {
|
||||||
const idsArray = [...ids.keys()];
|
const idsArray = [...ids.keys()];
|
||||||
|
|
||||||
const filesRaw = await trpc().file.bulkGet.query({ ids: idsArray });
|
const filesRaw = await trpc.file.bulkGet.query({ ids: idsArray });
|
||||||
const files = await Promise.all(
|
const files = await Promise.all(
|
||||||
filesRaw.map(async ({ id, categories: categoriesRaw, ...metadataRaw }) => {
|
filesRaw.map(async ({ id, categories: categoriesRaw, ...metadataRaw }) => {
|
||||||
const [categories, metadata] = await Promise.all([
|
const [categories, metadata] = await Promise.all([
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.url.pathname === "/auth/login") {
|
||||||
|
const redirectPath = event.url.searchParams.get("redirect");
|
||||||
|
// Allow same-origin paths only
|
||||||
|
redirect(302, redirectPath?.match(/^\/(?![/\\])/) ? redirectPath : "/home");
|
||||||
|
}
|
||||||
|
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const requestSessionUpgrade = async (
|
|||||||
) => {
|
) => {
|
||||||
let id, challenge;
|
let id, challenge;
|
||||||
try {
|
try {
|
||||||
({ id, challenge } = await trpc().auth.upgrade.mutate({
|
({ id, challenge } = await trpc.auth.upgrade.mutate({
|
||||||
encPubKey: encryptKeyBase64,
|
encPubKey: encryptKeyBase64,
|
||||||
sigPubKey: verifyKeyBase64,
|
sigPubKey: verifyKeyBase64,
|
||||||
}));
|
}));
|
||||||
@@ -24,7 +24,7 @@ export const requestSessionUpgrade = async (
|
|||||||
const answerSig = await signMessageRSA(answer, signKey);
|
const answerSig = await signMessageRSA(answer, signKey);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await trpc().auth.verifyUpgrade.mutate({
|
await trpc.auth.verifyUpgrade.mutate({
|
||||||
id,
|
id,
|
||||||
answerSig: encodeToBase64(answerSig),
|
answerSig: encodeToBase64(answerSig),
|
||||||
force,
|
force,
|
||||||
@@ -41,7 +41,7 @@ export const requestSessionUpgrade = async (
|
|||||||
|
|
||||||
export const requestLogout = async () => {
|
export const requestLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await trpc().auth.logout.mutate();
|
await trpc.auth.logout.mutate();
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const requestCategoryCreation = async (
|
|||||||
const nameEncrypted = await encryptString(name, dataKey);
|
const nameEncrypted = await encryptString(name, dataKey);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await trpc().category.create.mutate({
|
await trpc.category.create.mutate({
|
||||||
parent: parentId,
|
parent: parentId,
|
||||||
mekVersion: masterKey.version,
|
mekVersion: masterKey.version,
|
||||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||||
@@ -28,7 +28,7 @@ export const requestCategoryCreation = async (
|
|||||||
|
|
||||||
export const requestFileRemovalFromCategory = async (fileId: number, categoryId: number) => {
|
export const requestFileRemovalFromCategory = async (fileId: number, categoryId: number) => {
|
||||||
try {
|
try {
|
||||||
await trpc().category.removeFile.mutate({ id: categoryId, file: fileId });
|
await trpc.category.removeFile.mutate({ id: categoryId, file: fileId });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ export const requestFileThumbnailUpload = async (
|
|||||||
dataKeyVersion: Date,
|
dataKeyVersion: Date,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const { uploadId } = await trpc().upload.startFileThumbnailUpload.mutate({
|
const { uploadId } = await trpc.upload.startFileThumbnailUpload.mutate({
|
||||||
file: fileId,
|
file: fileId,
|
||||||
dekVersion: dataKeyVersion,
|
dekVersion: dataKeyVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
await uploadBlob(uploadId, thumbnail, dataKey);
|
await uploadBlob(uploadId, thumbnail, dataKey);
|
||||||
|
|
||||||
await trpc().upload.completeFileThumbnailUpload.mutate({ uploadId });
|
await trpc.upload.completeFileThumbnailUpload.mutate({ uploadId });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
@@ -47,7 +47,7 @@ export const requestFileThumbnailUpload = async (
|
|||||||
export const requestDeletedFilesCleanup = async () => {
|
export const requestDeletedFilesCleanup = async () => {
|
||||||
let liveFiles;
|
let liveFiles;
|
||||||
try {
|
try {
|
||||||
liveFiles = await trpc().file.list.query();
|
liveFiles = await trpc.file.list.query();
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ export const requestClientRegistration = async (
|
|||||||
signKey: CryptoKey,
|
signKey: CryptoKey,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const { id, challenge } = await trpc().client.register.mutate({
|
const { id, challenge } = await trpc.client.register.mutate({
|
||||||
encPubKey: encryptKeyBase64,
|
encPubKey: encryptKeyBase64,
|
||||||
sigPubKey: verifyKeyBase64,
|
sigPubKey: verifyKeyBase64,
|
||||||
});
|
});
|
||||||
const answer = await decryptChallenge(challenge, decryptKey);
|
const answer = await decryptChallenge(challenge, decryptKey);
|
||||||
const answerSig = await signMessageRSA(answer, signKey);
|
const answerSig = await signMessageRSA(answer, signKey);
|
||||||
await trpc().client.verify.mutate({
|
await trpc.client.verify.mutate({
|
||||||
id,
|
id,
|
||||||
answerSig: encodeToBase64(answerSig),
|
answerSig: encodeToBase64(answerSig),
|
||||||
});
|
});
|
||||||
@@ -68,7 +68,7 @@ export const requestClientRegistrationAndSessionUpgrade = async (
|
|||||||
export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: CryptoKey) => {
|
export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: CryptoKey) => {
|
||||||
let masterKeysWrapped;
|
let masterKeysWrapped;
|
||||||
try {
|
try {
|
||||||
masterKeysWrapped = await trpc().mek.list.query();
|
masterKeysWrapped = await trpc.mek.list.query();
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
return false;
|
return false;
|
||||||
@@ -106,7 +106,7 @@ export const requestInitialMasterKeyAndHmacSecretRegistration = async (
|
|||||||
signKey: CryptoKey,
|
signKey: CryptoKey,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await trpc().mek.registerInitial.mutate({
|
await trpc.mek.registerInitial.mutate({
|
||||||
mek: masterKeyWrapped,
|
mek: masterKeyWrapped,
|
||||||
mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey),
|
mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey),
|
||||||
});
|
});
|
||||||
@@ -119,7 +119,7 @@ export const requestInitialMasterKeyAndHmacSecretRegistration = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await trpc().hsk.registerInitial.mutate({
|
await trpc.hsk.registerInitial.mutate({
|
||||||
mekVersion: 1,
|
mekVersion: 1,
|
||||||
hsk: hmacSecretWrapped,
|
hsk: hmacSecretWrapped,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { trpc } from "$trpc/client";
|
|||||||
|
|
||||||
export const requestPasswordChange = async (oldPassword: string, newPassword: string) => {
|
export const requestPasswordChange = async (oldPassword: string, newPassword: string) => {
|
||||||
try {
|
try {
|
||||||
await trpc().auth.changePassword.mutate({ oldPassword, newPassword });
|
await trpc.auth.changePassword.mutate({ oldPassword, newPassword });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals, url }) => {
|
|
||||||
const redirectPath = url.searchParams.get("redirect") || "/home";
|
|
||||||
|
|
||||||
if (locals.session) {
|
|
||||||
redirect(302, redirectPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { redirectPath };
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ url }) => {
|
||||||
|
const redirectPath = url.searchParams.get("redirect") || "/home";
|
||||||
|
return { redirectPath };
|
||||||
|
};
|
||||||
@@ -9,7 +9,7 @@ export {
|
|||||||
|
|
||||||
export const requestLogin = async (email: string, password: string) => {
|
export const requestLogin = async (email: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
await trpc().auth.login.mutate({ email, password });
|
await trpc.auth.login.mutate({ email, password });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const requestThumbnailUpload = async (
|
|||||||
|
|
||||||
export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => {
|
export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => {
|
||||||
try {
|
try {
|
||||||
await trpc().category.addFile.mutate({ id: categoryId, file: fileId });
|
await trpc.category.addFile.mutate({ id: categoryId, file: fileId });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
@@ -52,9 +52,9 @@ export const requestFileAdditionToCategory = async (fileId: number, categoryId:
|
|||||||
export const requestFavoriteToggle = async (fileId: number, isFavorite: boolean) => {
|
export const requestFavoriteToggle = async (fileId: number, isFavorite: boolean) => {
|
||||||
try {
|
try {
|
||||||
if (isFavorite) {
|
if (isFavorite) {
|
||||||
await trpc().favorites.removeFile.mutate({ id: fileId });
|
await trpc.favorites.removeFile.mutate({ id: fileId });
|
||||||
} else {
|
} else {
|
||||||
await trpc().favorites.addFile.mutate({ id: fileId });
|
await trpc.favorites.addFile.mutate({ id: fileId });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createCaller } from "$trpc/router.server";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
|
||||||
const files = await createCaller(event).file.list();
|
|
||||||
return { files };
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { trpc } from "$trpc/client";
|
||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ parent }) => {
|
||||||
|
await parent(); // Wait for the key guard in the root layout
|
||||||
|
const files = await trpc.file.list.query();
|
||||||
|
return { files };
|
||||||
|
};
|
||||||
@@ -20,7 +20,7 @@ export interface SearchResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => {
|
export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => {
|
||||||
const { directories: directoriesRaw, files: filesRaw } = await trpc().search.search.query({
|
const { directories: directoriesRaw, files: filesRaw } = await trpc.search.search.query({
|
||||||
ancestor: filter.ancestorId,
|
ancestor: filter.ancestorId,
|
||||||
inFavorites: filter.inFavorites,
|
inFavorites: filter.inFavorites,
|
||||||
includeCategories: filter.categories
|
includeCategories: filter.categories
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createCaller } from "$trpc/router.server";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
|
||||||
const files = await createCaller(event).file.listWithoutThumbnail();
|
|
||||||
return { files };
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { trpc } from "$trpc/client";
|
||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ parent }) => {
|
||||||
|
await parent(); // Wait for the key guard in the root layout
|
||||||
|
const files = await trpc.file.listWithoutThumbnail.query();
|
||||||
|
return { files };
|
||||||
|
};
|
||||||
@@ -30,7 +30,7 @@ export const requestCategoryRename = async (category: SelectedCategory, newName:
|
|||||||
const newNameEncrypted = await encryptString(newName, category.dataKey.key);
|
const newNameEncrypted = await encryptString(newName, category.dataKey.key);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await trpc().category.rename.mutate({
|
await trpc.category.rename.mutate({
|
||||||
id: category.id,
|
id: category.id,
|
||||||
dekVersion: category.dataKey.version,
|
dekVersion: category.dataKey.version,
|
||||||
name: newNameEncrypted.ciphertext,
|
name: newNameEncrypted.ciphertext,
|
||||||
@@ -45,7 +45,7 @@ export const requestCategoryRename = async (category: SelectedCategory, newName:
|
|||||||
|
|
||||||
export const requestCategoryDeletion = async (category: SelectedCategory) => {
|
export const requestCategoryDeletion = async (category: SelectedCategory) => {
|
||||||
try {
|
try {
|
||||||
await trpc().category.delete.mutate({ id: category.id });
|
await trpc.category.delete.mutate({ id: category.id });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const requestHmacSecretDownload = async (masterKey: CryptoKey) => {
|
|||||||
|
|
||||||
let hmacSecretsWrapped;
|
let hmacSecretsWrapped;
|
||||||
try {
|
try {
|
||||||
hmacSecretsWrapped = await trpc().hsk.list.query();
|
hmacSecretsWrapped = await trpc.hsk.list.query();
|
||||||
} catch {
|
} catch {
|
||||||
// TODO: Error Handling
|
// TODO: Error Handling
|
||||||
return false;
|
return false;
|
||||||
@@ -64,7 +64,7 @@ export const requestDirectoryCreation = async (
|
|||||||
const nameEncrypted = await encryptString(name, dataKey);
|
const nameEncrypted = await encryptString(name, dataKey);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await trpc().directory.create.mutate({
|
await trpc.directory.create.mutate({
|
||||||
parent: parentId,
|
parent: parentId,
|
||||||
mekVersion: masterKey.version,
|
mekVersion: masterKey.version,
|
||||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||||
@@ -109,14 +109,14 @@ export const requestEntryRename = async (entry: SelectedEntry, newName: string)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (entry.type === "directory") {
|
if (entry.type === "directory") {
|
||||||
await trpc().directory.rename.mutate({
|
await trpc.directory.rename.mutate({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
dekVersion: entry.dataKey.version,
|
dekVersion: entry.dataKey.version,
|
||||||
name: newNameEncrypted.ciphertext,
|
name: newNameEncrypted.ciphertext,
|
||||||
nameIv: newNameEncrypted.iv,
|
nameIv: newNameEncrypted.iv,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await trpc().file.rename.mutate({
|
await trpc.file.rename.mutate({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
dekVersion: entry.dataKey.version,
|
dekVersion: entry.dataKey.version,
|
||||||
name: newNameEncrypted.ciphertext,
|
name: newNameEncrypted.ciphertext,
|
||||||
@@ -133,7 +133,7 @@ export const requestEntryRename = async (entry: SelectedEntry, newName: string)
|
|||||||
export const requestEntryDeletion = async (entry: SelectedEntry) => {
|
export const requestEntryDeletion = async (entry: SelectedEntry) => {
|
||||||
try {
|
try {
|
||||||
if (entry.type === "directory") {
|
if (entry.type === "directory") {
|
||||||
const { deletedFiles } = await trpc().directory.delete.mutate({ id: entry.id });
|
const { deletedFiles } = await trpc.directory.delete.mutate({ id: entry.id });
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
deletedFiles.flatMap((fileId) => [
|
deletedFiles.flatMap((fileId) => [
|
||||||
deleteFileCache(fileId),
|
deleteFileCache(fileId),
|
||||||
@@ -141,7 +141,7 @@ export const requestEntryDeletion = async (entry: SelectedEntry) => {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await trpc().file.delete.mutate({ id: entry.id });
|
await trpc.file.delete.mutate({ id: entry.id });
|
||||||
await Promise.all([deleteFileCache(entry.id), deleteFileThumbnailCache(entry.id)]);
|
await Promise.all([deleteFileCache(entry.id), deleteFileThumbnailCache(entry.id)]);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -155,15 +155,15 @@ export const requestFavoriteToggle = async (entry: SelectedEntry) => {
|
|||||||
try {
|
try {
|
||||||
if (entry.type === "directory") {
|
if (entry.type === "directory") {
|
||||||
if (entry.isFavorite) {
|
if (entry.isFavorite) {
|
||||||
await trpc().favorites.removeDirectory.mutate({ id: entry.id });
|
await trpc.favorites.removeDirectory.mutate({ id: entry.id });
|
||||||
} else {
|
} else {
|
||||||
await trpc().favorites.addDirectory.mutate({ id: entry.id });
|
await trpc.favorites.addDirectory.mutate({ id: entry.id });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entry.isFavorite) {
|
if (entry.isFavorite) {
|
||||||
await trpc().favorites.removeFile.mutate({ id: entry.id });
|
await trpc.favorites.removeFile.mutate({ id: entry.id });
|
||||||
} else {
|
} else {
|
||||||
await trpc().favorites.addFile.mutate({ id: entry.id });
|
await trpc.favorites.addFile.mutate({ id: entry.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createCaller } from "$trpc/router.server";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
|
||||||
const favorites = await createCaller(event).favorites.get();
|
|
||||||
return { favorites };
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { trpc } from "$trpc/client";
|
||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ parent }) => {
|
||||||
|
await parent(); // Wait for the key guard in the root layout
|
||||||
|
const favorites = await trpc.favorites.get.query();
|
||||||
|
return { favorites };
|
||||||
|
};
|
||||||
@@ -52,9 +52,9 @@ export const requestFavoriteEntries = async (
|
|||||||
export const requestRemoveFavorite = async (type: "file" | "directory", id: number) => {
|
export const requestRemoveFavorite = async (type: "file" | "directory", id: number) => {
|
||||||
try {
|
try {
|
||||||
if (type === "directory") {
|
if (type === "directory") {
|
||||||
await trpc().favorites.removeDirectory.mutate({ id });
|
await trpc.favorites.removeDirectory.mutate({ id });
|
||||||
} else {
|
} else {
|
||||||
await trpc().favorites.removeFile.mutate({ id });
|
await trpc.favorites.removeFile.mutate({ id });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createCaller } from "$trpc/router.server";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
|
||||||
const { nickname } = await createCaller(event).user.get();
|
|
||||||
return { nickname };
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { trpc } from "$trpc/client";
|
||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ parent }) => {
|
||||||
|
await parent(); // Wait for the key guard in the root layout
|
||||||
|
const { nickname } = await trpc.user.get.query();
|
||||||
|
return { nickname };
|
||||||
|
};
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { goto as svelteGoto } from "$app/navigation";
|
|
||||||
import { getDownloadingFiles, getUploadingFiles } from "$lib/modules/file";
|
import { getDownloadingFiles, getUploadingFiles } from "$lib/modules/file";
|
||||||
import { clientKeyStore, masterKeyStore } from "$lib/stores";
|
|
||||||
import "../app.css";
|
import "../app.css";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
@@ -12,23 +9,6 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const goto = async (url: string) => {
|
|
||||||
const whitelist = ["/auth/login", "/key", "/client/pending"];
|
|
||||||
if (!whitelist.some((path) => location.pathname.startsWith(path))) {
|
|
||||||
await svelteGoto(
|
|
||||||
`${url}?redirect=${encodeURIComponent(location.pathname + location.search)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!$clientKeyStore) {
|
|
||||||
await goto("/key/generate");
|
|
||||||
} else if (!$masterKeyStore) {
|
|
||||||
await goto("/client/pending");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window onbeforeunload={protectFileUploadAndDownload} />
|
<svelte:window onbeforeunload={protectFileUploadAndDownload} />
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import { clientKeyStore, masterKeyStore } from "$lib/stores";
|
||||||
|
import type { LayoutLoad } from "./$types";
|
||||||
|
|
||||||
|
export const ssr = false;
|
||||||
|
|
||||||
|
const KEYLESS_PATHS = ["/auth/login", "/key", "/client/pending"];
|
||||||
|
|
||||||
|
export const load: LayoutLoad = ({ url }) => {
|
||||||
|
if (KEYLESS_PATHS.some((path) => url.pathname.startsWith(path))) return;
|
||||||
|
|
||||||
|
const redirectQuery = `?redirect=${encodeURIComponent(url.pathname + url.search)}`;
|
||||||
|
if (!get(clientKeyStore)) {
|
||||||
|
redirect(307, "/key/generate" + redirectQuery);
|
||||||
|
} else if (!get(masterKeyStore)) {
|
||||||
|
redirect(307, "/client/pending" + redirectQuery);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = () => {
|
||||||
|
redirect(302, "/home");
|
||||||
|
};
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
import type { RequestHandler } from "./$types";
|
|
||||||
|
|
||||||
export const GET: RequestHandler = () => {
|
|
||||||
redirect(302, "/home");
|
|
||||||
};
|
|
||||||
+31
-15
@@ -1,31 +1,47 @@
|
|||||||
import { createTRPCClient, httpBatchLink, TRPCClientError } from "@trpc/client";
|
import { createTRPCClient, httpBatchLink, TRPCClientError, type TRPCLink } from "@trpc/client";
|
||||||
|
import { observable } from "@trpc/server/observable";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { browser } from "$app/environment";
|
import { goto } from "$app/navigation";
|
||||||
|
import { navigating } from "$app/state";
|
||||||
import type { AppRouter } from "./router.server";
|
import type { AppRouter } from "./router.server";
|
||||||
|
|
||||||
const createClient = (fetch: typeof globalThis.fetch) =>
|
// Procedures that report invalid credentials as UNAUTHORIZED, not an expired session
|
||||||
createTRPCClient<AppRouter>({
|
const CREDENTIAL_PROCEDURES = new Set(["auth.login", "auth.upgrade"]);
|
||||||
|
|
||||||
|
const sessionExpiryLink: TRPCLink<AppRouter> = () => {
|
||||||
|
return ({ op, next }) =>
|
||||||
|
observable((observer) =>
|
||||||
|
next(op).subscribe({
|
||||||
|
next: (value) => observer.next(value),
|
||||||
|
error: (err) => {
|
||||||
|
// Prefer the destination when the request failed during a navigation's load
|
||||||
|
const url = navigating.to?.url ?? location;
|
||||||
|
if (
|
||||||
|
err.data?.code === "UNAUTHORIZED" &&
|
||||||
|
!CREDENTIAL_PROCEDURES.has(op.path) &&
|
||||||
|
url.pathname !== "/auth/login"
|
||||||
|
) {
|
||||||
|
void goto(`/auth/login?redirect=${encodeURIComponent(url.pathname + url.search)}`);
|
||||||
|
}
|
||||||
|
observer.error(err);
|
||||||
|
},
|
||||||
|
complete: () => observer.complete(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trpc = createTRPCClient<AppRouter>({
|
||||||
links: [
|
links: [
|
||||||
|
sessionExpiryLink,
|
||||||
httpBatchLink({
|
httpBatchLink({
|
||||||
url: "/api/trpc",
|
url: "/api/trpc",
|
||||||
maxURLLength: 4096,
|
maxURLLength: 4096,
|
||||||
methodOverride: "POST",
|
methodOverride: "POST",
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
fetch,
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let browserClient: ReturnType<typeof createClient>;
|
|
||||||
|
|
||||||
export const trpc = (fetch = globalThis.fetch) => {
|
|
||||||
const client = browserClient ?? createClient(fetch);
|
|
||||||
if (browser) {
|
|
||||||
browserClient ??= client;
|
|
||||||
}
|
|
||||||
return client;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isTRPCClientError = (e: unknown): e is TRPCClientError<AppRouter> => {
|
export const isTRPCClientError = (e: unknown): e is TRPCClientError<AppRouter> => {
|
||||||
return e instanceof TRPCClientError;
|
return e instanceof TRPCClientError;
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { RequestEvent } from "@sveltejs/kit";
|
|
||||||
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
||||||
import { createContext, router } from "./init.server";
|
import { router } from "./init.server";
|
||||||
import {
|
import {
|
||||||
authRouter,
|
authRouter,
|
||||||
categoryRouter,
|
categoryRouter,
|
||||||
@@ -29,8 +28,6 @@ export const appRouter = router({
|
|||||||
user: userRouter,
|
user: userRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createCaller = (event: RequestEvent) => appRouter.createCaller(createContext(event));
|
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
export type RouterInputs = inferRouterInputs<AppRouter>;
|
export type RouterInputs = inferRouterInputs<AppRouter>;
|
||||||
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
||||||
Reference in new issue
Block a user