diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-07-21 00:33:33 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-07-21 00:33:33 +0000 |
| commit | bb11907e8b04d519c4ea3e67d4c7ce5a6226914b (patch) | |
| tree | 4dd81c88aad12ee9b6db2a81e0190baac56a6b31 /packages/trpc/auth.ts | |
| parent | 52ac0869d53b54e91db557f012f7ee9a3ecc3e9d (diff) | |
| download | karakeep-bb11907e8b04d519c4ea3e67d4c7ce5a6226914b.tar.zst | |
fix: Remove bcrypt from the api key validation route
Diffstat (limited to 'packages/trpc/auth.ts')
| -rw-r--r-- | packages/trpc/auth.ts | 55 |
1 files changed, 39 insertions, 16 deletions
diff --git a/packages/trpc/auth.ts b/packages/trpc/auth.ts index a01288d8..01966b9e 100644 --- a/packages/trpc/auth.ts +++ b/packages/trpc/auth.ts @@ -1,28 +1,33 @@ -import { randomBytes } from "crypto"; +import { createHash, randomBytes } from "crypto"; import * as bcrypt from "bcryptjs"; -import { db } from "@karakeep/db"; import { apiKeys } from "@karakeep/db/schema"; import serverConfig from "@karakeep/shared/config"; -// API Keys +import type { Context } from "./index"; const BCRYPT_SALT_ROUNDS = 10; -const API_KEY_PREFIX = "ak1"; +const API_KEY_PREFIX_V1 = "ak1"; +const API_KEY_PREFIX_V2 = "ak2"; export function generatePasswordSalt() { return randomBytes(32).toString("hex"); } -export async function generateApiKey(name: string, userId: string) { +export async function generateApiKey( + name: string, + userId: string, + database: Context["db"], +) { const id = randomBytes(10).toString("hex"); - const secret = randomBytes(10).toString("hex"); - const secretHash = await bcrypt.hash(secret, BCRYPT_SALT_ROUNDS); + const secret = randomBytes(16).toString("hex"); - const plain = `${API_KEY_PREFIX}_${id}_${secret}`; + const secretHash = createHash("sha256").update(secret).digest("base64"); + + const plain = `${API_KEY_PREFIX_V2}_${id}_${secret}`; const key = ( - await db + await database .insert(apiKeys) .values({ name: name, @@ -40,6 +45,7 @@ export async function generateApiKey(name: string, userId: string) { key: plain, }; } + function parseApiKey(plain: string) { const parts = plain.split("_"); if (parts.length != 3) { @@ -47,18 +53,19 @@ function parseApiKey(plain: string) { `Malformd API key. API keys should have 3 segments, found ${parts.length} instead.`, ); } - if (parts[0] !== API_KEY_PREFIX) { + if (parts[0] !== API_KEY_PREFIX_V1 && parts[0] !== API_KEY_PREFIX_V2) { throw new Error(`Malformd API key. Got unexpected key prefix.`); } return { + version: parts[0] == API_KEY_PREFIX_V1 ? (1 as const) : (2 as const), keyId: parts[1], keySecret: parts[2], }; } -export async function authenticateApiKey(key: string) { - const { keyId, keySecret } = parseApiKey(key); - const apiKey = await db.query.apiKeys.findFirst({ +export async function authenticateApiKey(key: string, database: Context["db"]) { + const { version, keyId, keySecret } = parseApiKey(key); + const apiKey = await database.query.apiKeys.findFirst({ where: (k, { eq }) => eq(k.keyId, keyId), with: { user: true, @@ -71,7 +78,19 @@ export async function authenticateApiKey(key: string) { const hash = apiKey.keyHash; - const validation = await bcrypt.compare(keySecret, hash); + let validation = false; + switch (version) { + case 1: + validation = await bcrypt.compare(keySecret, hash); + break; + case 2: + validation = + createHash("sha256").update(keySecret).digest("base64") == hash; + break; + default: + throw new Error("Invalid API Key"); + } + if (!validation) { throw new Error("Invalid API Key"); } @@ -83,11 +102,15 @@ export async function hashPassword(password: string, salt: string | null) { return await bcrypt.hash(password + (salt ?? ""), BCRYPT_SALT_ROUNDS); } -export async function validatePassword(email: string, password: string) { +export async function validatePassword( + email: string, + password: string, + database: Context["db"], +) { if (serverConfig.auth.disablePasswordAuth) { throw new Error("Password authentication is currently disabled"); } - const user = await db.query.users.findFirst({ + const user = await database.query.users.findFirst({ where: (u, { eq }) => eq(u.email, email), }); |
