diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-05 13:11:06 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-05 13:11:06 +0000 |
| commit | 8a46ecb7373d6c5e7300861169ea51a7917cd2b4 (patch) | |
| tree | 4ad318c3b5fc8b7a74cba6d0e37b6ade24db829a /packages/trpc/auth.ts | |
| parent | 224aa38d5976523f213e2860b6addc7630d472ba (diff) | |
| download | karakeep-8a46ecb7373d6c5e7300861169ea51a7917cd2b4.tar.zst | |
refactor: Extract trpc logic into its package
Diffstat (limited to 'packages/trpc/auth.ts')
| -rw-r--r-- | packages/trpc/auth.ts | 99 |
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; +} |
