Skip to content

Commit

Permalink
ok file download works (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
d3rpp authored Nov 19, 2024
1 parent ce60bff commit de945db
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 199 deletions.
154 changes: 154 additions & 0 deletions src/lib/components/ui/file-card/file-card.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<script lang="ts">
import type { trpc } from "$lib/trpc/client";
import * as Card from "@/ui/card";
import * as Tooltip from "@/ui/tooltip";
import { Button } from "@/ui/button";
import { filesize } from "filesize";
import { AnimatedLoading } from "$lib/icons";
import { Download, X } from "lucide-svelte";
import { import_private_key } from "$lib/client/key_management";
import {
unwrap_symmetrical_key,
decrypt_blob,
} from "$lib/client/encryption";
interface File {
name: string;
size: number;
id: string;
owner: boolean;
iv: string;
}
const {
file,
rpc,
}: {
file: File;
rpc: ReturnType<typeof trpc>;
} = $props();
// screw it, we'll just download ALL OF THEM EVERY TIME
const symkeyQuery = rpc.blobManagement.downloadSymmeticKey.createQuery({
kid: file.id,
});
let isBusy = $state(false);
let isLoading = $derived(
isBusy || $symkeyQuery.isLoading || $symkeyQuery.isError,
);
const decodeB64Blob = (b64: string): Uint8Array => {
const binaryString = atob(b64);
let bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
};
const downloadFile = async () => {
try {
isBusy = true;
console.group("Downloading File");
const key_data = $symkeyQuery.data;
if (!key_data) return;
const { key_b64, pubkey } = key_data;
console.log("Got past we have key check");
const wrapped_key = decodeB64Blob(key_b64 as string);
const unwraping_key = await import_private_key(pubkey);
console.log("unwrapping symmetrical key");
const unwrapped_key = await unwrap_symmetrical_key(
unwraping_key,
wrapped_key,
);
console.log("fetching encrypted blob");
const response = await fetch(`/api/blobs/${file.id}`);
const response_blob = await response.arrayBuffer();
console.log("decrypting blob");
const iv = decodeB64Blob(file.iv);
const decrypted_blob = await decrypt_blob(
unwrapped_key,
iv,
response_blob,
);
console.log("downloading blob");
const file_blob = new Blob([decrypted_blob]);
const url = window.URL.createObjectURL(file_blob);
const anchor = document.createElement("a");
anchor.style.display = "none";
anchor.setAttribute("href", url);
anchor.setAttribute("download", file.name);
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
console.info("Done");
} catch (e) {
console.info("Failed");
console.error(e);
}
console.groupEnd();
isBusy = false;
};
</script>

<Card.Root class="w-72">
<Card.Header>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<div
class="overflow-hidden text-ellipsis text-nowrap"
{...props}
>
{file.name}
</div>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>
<p>{file.name}</p>
</Tooltip.Content>
</Tooltip.Root>
</Card.Header>
<Card.Content>
{filesize(file.size)}
</Card.Content>
<Card.Footer>
<Button
size="icon"
variant="ghost"
disabled={isLoading}
onmouseup={downloadFile}
>
{#if isLoading}
<AnimatedLoading />
{:else if $symkeyQuery.isError}
<X class="text-destructive-foreground" />
{:else}
<Download />
{/if}
</Button>
</Card.Footer>
</Card.Root>
3 changes: 3 additions & 0 deletions src/lib/components/ui/file-card/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FileCard from "./file-card.svelte";

export { FileCard };
114 changes: 114 additions & 0 deletions src/lib/server/trpc/router/blob_management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { DB } from "$lib/server/db";
import { authMiddleware } from "../middleware";
import { trpcInstance } from "./init";

import {
publicKeyTable,
encryptedBlobTable,
symmetricKeyTable,
} from "$lib/drizzle";
import { and, eq } from "drizzle-orm";

import z from "zod";
import { TRPCError } from "@trpc/server";
import { monotonic_ulid } from "$lib/utils";

export const blobManagementRouter = trpcInstance.router({
// #region Symmetric Key Upload
uploadSymmetricKeyAndCreateEmptyBlob: trpcInstance.procedure
.use(authMiddleware)
.input(
z.object({
key_b64: z.string().base64(),
pubkey: z.string().ulid(),
file_name: z.string().min(3).max(100),
iv_b64: z.string().base64(),
}),
)
.mutation(async ({ ctx, input }) => {
return await DB.transaction(async (tx_db) => {
const keyCount = (
await tx_db
.selectDistinct({ kid: publicKeyTable.kid })
.from(publicKeyTable)
.where(
and(
eq(publicKeyTable.kid, input.pubkey),
eq(publicKeyTable.keyOwner, ctx.user.id),
),
)
).length;

if (keyCount == 0) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No public key uploaded",
});
} else if (keyCount > 1) {
throw new TRPCError({
code: "CONFLICT",
message:
"Multiple keys uploaded with KID, don't know what to do.",
});
} else {
const [{ kid }] = await tx_db
.insert(symmetricKeyTable)
.values({
key: input.key_b64,
publicKey: input.pubkey,
kid: monotonic_ulid(),
})
.returning();

await tx_db.insert(encryptedBlobTable).values({
kid: kid,
name: input.file_name,
state: "fresh",
owner: ctx.user.id,
iv: input.iv_b64,
});

return kid;
}
});
}),
// #endregion
// #region Symmetric Key Download
downloadSymmeticKey: trpcInstance.procedure
.use(authMiddleware)
.input(
z.object({
kid: z.string().ulid(),
}),
)
.query(async (opts) => {
const keys = await DB.select({
key_b64: symmetricKeyTable.key,
pubkey: symmetricKeyTable.publicKey,
})
.from(symmetricKeyTable)
.leftJoin(
publicKeyTable,
eq(symmetricKeyTable.publicKey, publicKeyTable.kid),
)
.where(
and(
eq(publicKeyTable.keyOwner, opts.ctx.user.id),
eq(symmetricKeyTable.kid, opts.input.kid),
),
);

if (keys.length != 1) {
throw new TRPCError({
code: "NOT_FOUND",
message: "No key found",
});
} else {
return {
...keys[0],
key_b64: keys[0].key_b64 as string,
};
}
}),
// #endregion
});
2 changes: 2 additions & 0 deletions src/lib/server/trpc/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { greeterRouter } from "./greeter";
import { healthRouter } from "./health";
import { userRouter } from "./user";
import { keyManagementRouter } from "./key_management";
import { blobManagementRouter } from "./blob_management";

export const router = trpcInstance.router({
greeter: greeterRouter,
health: healthRouter,
user: userRouter,
keyManagement: keyManagementRouter,
blobManagement: blobManagementRouter,
});

export type Router = typeof router;
Expand Down
66 changes: 1 addition & 65 deletions src/lib/server/trpc/router/key_management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ import { authMiddleware } from "../middleware";
import { TRPCError } from "@trpc/server";

import { DB } from "$lib/server/db";
import {
encryptedBlobTable,
publicKeyTable,
reservedKIDTable,
symmetricKeyTable,
} from "$lib/drizzle";
import { publicKeyTable, reservedKIDTable } from "$lib/drizzle";
import { eq, and } from "drizzle-orm";

import { monotonic_ulid } from "$lib/utils";
Expand Down Expand Up @@ -287,63 +282,4 @@ export const keyManagementRouter = trpcInstance.router({
});
}),
// #endregion
// #region Symmetric Key Upload
uploadSymmetricKeyAndCreateEmptyBlob: trpcInstance.procedure
.use(authMiddleware)
.input(
z.object({
key_b64: z.string().base64(),
pubkey: z.string().ulid(),
file_name: z.string().min(3).max(100),
iv_b64: z.string().base64(),
}),
)
.mutation(async ({ ctx, input }) => {
return await DB.transaction(async (tx_db) => {
const keyCount = (
await tx_db
.selectDistinct({ kid: publicKeyTable.kid })
.from(publicKeyTable)
.where(
and(
eq(publicKeyTable.kid, input.pubkey),
eq(publicKeyTable.keyOwner, ctx.user.id),
),
)
).length;

if (keyCount == 0) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No public key uploaded",
});
} else if (keyCount > 1) {
throw new TRPCError({
code: "CONFLICT",
message:
"Multiple keys uploaded with KID, don't know what to do.",
});
} else {
const [{ kid }] = await tx_db
.insert(symmetricKeyTable)
.values({
key: input.key_b64,
publicKey: input.pubkey,
kid: monotonic_ulid(),
})
.returning();

await tx_db.insert(encryptedBlobTable).values({
kid: kid,
name: input.file_name,
state: "fresh",
owner: ctx.user.id,
iv: input.iv_b64,
});

return kid;
}
});
}),
// #endregion
});
Loading

0 comments on commit de945db

Please sign in to comment.