Files
exams/backend/src/routes/account/api-keys.ts
Space-Banane ead561d10b first commit
2026-02-09 19:26:40 +01:00

109 lines
3.7 KiB
TypeScript

import { COOKIENAME } from "../..";
import { db, fileRouter } from "../../";
import { authCheck } from "../../lib/Auth";
import crypto from "crypto";
export = new fileRouter.Path("/")
.http("GET", "/api/account/api-keys", (http) =>
http.onRequest(async (ctr) => {
const cookie = ctr.cookies.get(COOKIENAME) || null;
const apiHeader = (ctr.headers && ctr.headers.get)
? ctr.headers.get("api-authentication") || null
: null;
const auth = await authCheck(cookie, apiHeader);
if (!auth.state) {
return ctr.status(ctr.$status.UNAUTHORIZED).print({ error: auth.message });
}
const user = await db.collection("users").findOne({ id: auth.userId });
if (!user) {
return ctr.status(ctr.$status.NOT_FOUND).print({ error: "User not found" });
}
const keys = (user.apiKeys || []).map((k: any) => ({
id: k.id,
description: k.description,
createdAt: k.createdAt,
}));
return ctr.print({ success: true, apiKeys: keys });
})
)
.http("POST", "/api/account/api-keys/create", (http) =>
http.onRequest(async (ctr) => {
const cookie = ctr.cookies.get(COOKIENAME) || null;
const apiHeader = (ctr.headers && ctr.headers.get)
? ctr.headers.get("api-authentication") || null
: null;
const auth = await authCheck(cookie, apiHeader);
if (!auth.state) {
return ctr.status(ctr.$status.UNAUTHORIZED).print({ error: auth.message });
}
const [data, error] = await ctr.bindBody((z) =>
z.object({ description: z.string().min(1).max(200) })
);
if (!data) return ctr.status(ctr.$status.BAD_REQUEST).print({ error: error.toString() });
const user = await db.collection("users").findOne({ id: auth.userId });
if (!user) return ctr.status(ctr.$status.NOT_FOUND).print({ error: "User not found" });
const existing = user.apiKeys || [];
if (existing.length >= 4) {
return ctr.status(ctr.$status.BAD_REQUEST).print({ error: "API key limit reached (max 4)" });
}
const raw = crypto.randomBytes(32).toString("hex");
const keyHash = crypto.createHash("sha256").update(raw).digest("hex");
const keyId = crypto.randomUUID();
const newKey = {
id: keyId,
description: data.description,
keyHash,
createdAt: new Date(),
};
const res = await db.collection("users").updateOne(
{ id: auth.userId },
{ $push: { apiKeys: newKey } } as any,
{ upsert: false }
);
if (!res.acknowledged) {
return ctr.status(ctr.$status.INTERNAL_SERVER_ERROR).print({ error: "Failed to create API key" });
}
return ctr.print({ success: true, apiKey: { id: keyId, description: data.description, createdAt: newKey.createdAt, token: raw } });
})
)
.http("DELETE", "/api/account/api-keys/{id}", (http) =>
http.onRequest(async (ctr) => {
const cookie = ctr.cookies.get(COOKIENAME) || null;
const apiHeader = (ctr.headers && ctr.headers.get)
? ctr.headers.get("api-authentication") || null
: null;
const auth = await authCheck(cookie, apiHeader);
if (!auth.state) {
return ctr.status(ctr.$status.UNAUTHORIZED).print({ error: auth.message });
}
const keyId = ctr.params.get("id");
if (!keyId) return ctr.status(ctr.$status.BAD_REQUEST).print({ error: "Key id required" });
const res = await db.collection("users").updateOne(
{ id: auth.userId },
{ $pull: { apiKeys: { id: keyId } } } as any
);
if (!res.acknowledged) {
return ctr.status(ctr.$status.INTERNAL_SERVER_ERROR).print({ error: "Failed to delete API key" });
}
return ctr.print({ success: true });
})
);