aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/auth.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-07-21 00:33:33 +0000
committerMohamed Bassem <me@mbassem.com>2025-07-21 00:33:33 +0000
commitbb11907e8b04d519c4ea3e67d4c7ce5a6226914b (patch)
tree4dd81c88aad12ee9b6db2a81e0190baac56a6b31 /packages/trpc/auth.ts
parent52ac0869d53b54e91db557f012f7ee9a3ecc3e9d (diff)
downloadkarakeep-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.ts55
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),
});