-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
455 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import FileCard from "./file-card.svelte"; | ||
|
||
export { FileCard }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.