Andrew commited on
Commit
70025fa
·
1 Parent(s): b346517

feat(security): add AES-256-GCM encryption for user tokens

Browse files
Files changed (1) hide show
  1. src/lib/server/tokenEncryption.ts +72 -0
src/lib/server/tokenEncryption.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import crypto from "crypto";
2
+ import { config } from "./config";
3
+ import { logger } from "./logger";
4
+
5
+ const ALGORITHM = "aes-256-gcm";
6
+ const IV_LENGTH = 16;
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ const AUTH_TAG_LENGTH = 16;
9
+ const KEY_LENGTH = 32;
10
+
11
+ /**
12
+ * Gets the encryption key from config or generates a warning
13
+ */
14
+ function getEncryptionKey(): Buffer {
15
+ const key = config.HF_TOKEN_ENCRYPTION_KEY;
16
+ if (!key) {
17
+ logger.warn(
18
+ "HF_TOKEN_ENCRYPTION_KEY not set. Tokens will be stored unencrypted. Set this to a 32-byte hex string for production."
19
+ );
20
+ // For development, use a default key (not secure, but allows testing)
21
+ const defaultKey = crypto.randomBytes(KEY_LENGTH).toString("hex");
22
+ logger.warn(`Using temporary encryption key: ${defaultKey}`);
23
+ return Buffer.from(defaultKey.slice(0, KEY_LENGTH * 2), "hex");
24
+ }
25
+
26
+ if (key.length !== KEY_LENGTH * 2) {
27
+ throw new Error(`HF_TOKEN_ENCRYPTION_KEY must be exactly ${KEY_LENGTH * 2} characters`);
28
+ }
29
+
30
+ return Buffer.from(key, "hex");
31
+ }
32
+
33
+ /**
34
+ * Encrypts a token using AES-256-GCM with a random IV
35
+ */
36
+ export function encryptToken(token: string): string {
37
+ const key = getEncryptionKey();
38
+ const iv = crypto.randomBytes(IV_LENGTH);
39
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
40
+
41
+ let encrypted = cipher.update(token, "utf8", "hex");
42
+ encrypted += cipher.final("hex");
43
+
44
+ const authTag = cipher.getAuthTag();
45
+
46
+ // Combine IV + authTag + encrypted data
47
+ return iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted;
48
+ }
49
+
50
+ /**
51
+ * Decrypts a token that was encrypted with encryptToken
52
+ */
53
+ export function decryptToken(encryptedToken: string): string {
54
+ const key = getEncryptionKey();
55
+ const parts = encryptedToken.split(":");
56
+
57
+ if (parts.length !== 3) {
58
+ throw new Error("Invalid encrypted token format");
59
+ }
60
+
61
+ const iv = Buffer.from(parts[0], "hex");
62
+ const authTag = Buffer.from(parts[1], "hex");
63
+ const encrypted = parts[2];
64
+
65
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
66
+ decipher.setAuthTag(authTag);
67
+
68
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
69
+ decrypted += decipher.final("utf8");
70
+
71
+ return decrypted;
72
+ }