Skip to content

Commit ab00ce5

Browse files
committed
fix: ensure AES encryption key is correctly decoded from base64 before import
1 parent f62ea4c commit ab00ce5

File tree

6 files changed

+29
-29
lines changed

6 files changed

+29
-29
lines changed

apps/collabydraw/canvas-engine/CanvasEngine.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
WS_URL,
2626
} from "@/config/constants";
2727
import { MessageQueue } from "./MessageQueue";
28-
// import { getShapes } from "@/actions/shape";
2928
import { decryptData, encryptData } from "@/utils/crypto";
3029

3130
export class CanvasEngine {

apps/collabydraw/components/CreateRoomDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useTransition } from "react";
77
import { createRoom } from "@/actions/room";
88
import { toast } from "sonner";
99
import { useRouter } from "next/navigation";
10-
import { nanoid } from "nanoid";
10+
import { generateAESKey } from "@/utils/crypto";
1111

1212
export default function CreateRoomDialog({ open, onOpenChange }: { open: boolean, onOpenChange: (open: boolean) => void }) {
1313
const [isPending, startTransition] = useTransition();
@@ -18,7 +18,7 @@ export default function CreateRoomDialog({ open, onOpenChange }: { open: boolean
1818
try {
1919
const result = await createRoom();
2020
if (result.success && result.room?.id) {
21-
const encryptionKey = nanoid(20);
21+
const encryptionKey = await generateAESKey();
2222
const redirectURL = `/#room=${result.room?.id},${encryptionKey}`;
2323
router.push(redirectURL);
2424
onOpenChange(false);

apps/collabydraw/components/canvas/CanvasRoot.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ export default function CanvasRoot() {
6262
userName: session?.user?.name ?? null,
6363
token: session?.accessToken ?? null,
6464
};
65-
} else {
65+
} else if (status === "unauthenticated" && currentRoomParams) {
6666
window.alert(
6767
"You need to be logged in to join this collaborative room.\n\n" +
6868
"Please sign up or log in to your account to continue. " +
6969
"Collaborative features require authentication to ensure secure access and proper identification of participants."
7070
);
7171
setMode("standalone");
7272
router.push(`/auth/signin?callbackUrl=${hash}`);
73+
} else {
74+
setMode("standalone");
7375
}
7476
};
7577

@@ -176,7 +178,7 @@ export default function CanvasRoot() {
176178
setParticipants(updatedParticipants);
177179
} : null,
178180
mode === 'room' ? (connectionStatus) => setIsConnected(connectionStatus) : null,
179-
null
181+
userRef.current.encryptionKey
180182
);
181183
return engine;
182184
}, [canvasEngineState.canvasColor, mode]);

apps/collabydraw/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
"jsonwebtoken": "^9.0.2",
3030
"lucide-react": "^0.475.0",
3131
"motion": "^12.4.3",
32-
"nanoid": "^5.1.5",
3332
"next": "15.1.7",
3433
"next-auth": "^4.24.11",
3534
"next-themes": "^0.4.4",

apps/collabydraw/utils/crypto.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
1+
export async function generateAESKey(): Promise<string> {
2+
const key = crypto.getRandomValues(new Uint8Array(16));
3+
return btoa(String.fromCharCode(...key));
4+
}
5+
6+
function base64ToUint8Array(base64: string): Uint8Array {
7+
const binary = atob(base64);
8+
const bytes = new Uint8Array(binary.length);
9+
for (let i = 0; i < binary.length; i++) {
10+
bytes[i] = binary.charCodeAt(i);
11+
}
12+
return bytes;
13+
}
14+
115
export async function encryptData(
216
plainText: string,
317
key: string
418
): Promise<string> {
5-
const enc = new TextEncoder();
619
const iv = crypto.getRandomValues(new Uint8Array(12));
720
const alg = { name: "AES-GCM", iv };
821

9-
const cryptoKey = await crypto.subtle.importKey(
10-
"raw",
11-
enc.encode(key),
12-
alg,
13-
false,
14-
["encrypt"]
15-
);
22+
const rawKey = base64ToUint8Array(key);
23+
24+
const cryptoKey = await crypto.subtle.importKey("raw", rawKey, alg, false, [
25+
"encrypt",
26+
]);
1627

1728
const encrypted = await crypto.subtle.encrypt(
1829
alg,
1930
cryptoKey,
20-
enc.encode(plainText)
31+
new TextEncoder().encode(plainText)
2132
);
2233

2334
const combined = new Uint8Array(iv.byteLength + encrypted.byteLength);
@@ -31,22 +42,21 @@ export async function decryptData(
3142
cipherText: string,
3243
key: string
3344
): Promise<string> {
34-
const enc = new TextEncoder();
3545
const combined = Uint8Array.from(atob(cipherText), (c) => c.charCodeAt(0));
3646
const iv = combined.slice(0, 12);
3747
const data = combined.slice(12);
3848

49+
const rawKey = base64ToUint8Array(key);
3950
const alg = { name: "AES-GCM", iv };
4051

4152
const cryptoKey = await crypto.subtle.importKey(
4253
"raw",
43-
enc.encode(key),
54+
rawKey,
4455
alg,
4556
false,
4657
["decrypt"]
4758
);
4859

4960
const decrypted = await crypto.subtle.decrypt(alg, cryptoKey, data);
50-
5161
return new TextDecoder().decode(decrypted);
52-
}
62+
}

pnpm-lock.yaml

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)