aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/auth.ts
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-05 13:11:06 +0000
committerMohamedBassem <me@mbassem.com>2024-03-05 13:11:06 +0000
commit8a46ecb7373d6c5e7300861169ea51a7917cd2b4 (patch)
tree4ad318c3b5fc8b7a74cba6d0e37b6ade24db829a /packages/trpc/auth.ts
parent224aa38d5976523f213e2860b6addc7630d472ba (diff)
downloadkarakeep-8a46ecb7373d6c5e7300861169ea51a7917cd2b4.tar.zst
refactor: Extract trpc logic into its package
Diffstat (limited to 'packages/trpc/auth.ts')
-rw-r--r--packages/trpc/auth.ts99
1 files changed, 99 insertions, 0 deletions
diff --git a/packages/trpc/auth.ts b/packages/trpc/auth.ts
new file mode 100644
index 00000000..6854303b
--- /dev/null
+++ b/packages/trpc/auth.ts
@@ -0,0 +1,99 @@
+import { randomBytes } from "crypto";
+import { apiKeys } from "@hoarder/db/schema";
+import * as bcrypt from "bcrypt";
+import { db } from "@hoarder/db";
+
+// API Keys
+
+const BCRYPT_SALT_ROUNDS = 10;
+const API_KEY_PREFIX = "ak1";
+
+export async function generateApiKey(name: string, userId: string) {
+ const id = randomBytes(10).toString("hex");
+ const secret = randomBytes(10).toString("hex");
+ const secretHash = await bcrypt.hash(secret, BCRYPT_SALT_ROUNDS);
+
+ const plain = `${API_KEY_PREFIX}_${id}_${secret}`;
+
+ const key = (
+ await db
+ .insert(apiKeys)
+ .values({
+ name: name,
+ userId: userId,
+ keyId: id,
+ keyHash: secretHash,
+ })
+ .returning()
+ )[0];
+
+ return {
+ id: key.id,
+ name: key.name,
+ createdAt: key.createdAt,
+ key: plain,
+ };
+}
+function parseApiKey(plain: string) {
+ const parts = plain.split("_");
+ if (parts.length != 3) {
+ throw new Error(
+ `Malformd API key. API keys should have 3 segments, found ${parts.length} instead.`,
+ );
+ }
+ if (parts[0] !== API_KEY_PREFIX) {
+ throw new Error(`Malformd API key. Got unexpected key prefix.`);
+ }
+ return {
+ keyId: parts[1],
+ keySecret: parts[2],
+ };
+}
+
+export async function authenticateApiKey(key: string) {
+ const { keyId, keySecret } = parseApiKey(key);
+ const apiKey = await db.query.apiKeys.findFirst({
+ where: (k, { eq }) => eq(k.keyId, keyId),
+ with: {
+ user: true,
+ },
+ });
+
+ if (!apiKey) {
+ throw new Error("API key not found");
+ }
+
+ const hash = apiKey.keyHash;
+
+ const validation = await bcrypt.compare(keySecret, hash);
+ if (!validation) {
+ throw new Error("Invalid API Key");
+ }
+
+ return apiKey.user;
+}
+
+export async function hashPassword(password: string) {
+ return bcrypt.hash(password, BCRYPT_SALT_ROUNDS);
+}
+
+export async function validatePassword(email: string, password: string) {
+ const user = await db.query.users.findFirst({
+ where: (u, { eq }) => eq(u.email, email),
+ });
+
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ if (!user.password) {
+ throw new Error("This user doesn't have a password defined");
+ }
+
+ const validation = await bcrypt.compare(password, user.password);
+ if (!validation) {
+ throw new Error("Wrong password");
+ }
+
+ return user;
+}