aboutsummaryrefslogtreecommitdiffstats
path: root/packages/web/server/auth.ts
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-02-12 14:52:00 +0000
committerMohamedBassem <me@mbassem.com>2024-02-12 14:55:00 +0000
commit6aacc0c7a86e36c52a3c2c1d26fe58cefcd3bec4 (patch)
treebad306e872d6bfcc2c67f00caa3880c8aa56070f /packages/web/server/auth.ts
parent230cafb6dfc8d3bad57d84ef13c3669f5bf5331a (diff)
downloadkarakeep-6aacc0c7a86e36c52a3c2c1d26fe58cefcd3bec4.tar.zst
feature: Add support for managing API keys
Diffstat (limited to 'packages/web/server/auth.ts')
-rw-r--r--packages/web/server/auth.ts75
1 files changed, 75 insertions, 0 deletions
diff --git a/packages/web/server/auth.ts b/packages/web/server/auth.ts
index 05d3d296..f78fa8cf 100644
--- a/packages/web/server/auth.ts
+++ b/packages/web/server/auth.ts
@@ -4,6 +4,9 @@ import AuthentikProvider from "next-auth/providers/authentik";
import serverConfig from "@/server/config";
import { prisma } from "@remember/db";
import { DefaultSession } from "next-auth";
+import * as bcrypt from "bcrypt";
+
+import { randomBytes } from "crypto";
declare module "next-auth" {
/**
@@ -37,3 +40,75 @@ export const authOptions: NextAuthOptions = {
export const authHandler = NextAuth(authOptions);
export const getServerAuthSession = () => getServerSession(authOptions);
+
+// 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 prisma.apiKey.create({
+ data: {
+ name: name,
+ userId: userId,
+ keyId: id,
+ keyHash: secretHash,
+ },
+ });
+
+ 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 prisma.apiKey.findUnique({
+ where: {
+ keyId,
+ },
+ include: {
+ 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 {
+ user: apiKey.user,
+ };
+}