"use server" import util from "util" import { createHash, type Hash, randomBytes } from "crypto" import { prisma } from "./index" import { cf_api_keys } from "@prisma/client" // const prisma = new PrismaClient() async function generateRandomAPIToken(): Promise { const randomBytesAsync = util.promisify(randomBytes) const token = (await randomBytesAsync(48)).toString("base64url") return "cf-" + token } export async function hashToken(token: string) { const hash: Hash = createHash("sha384") hash.update(token) return hash.digest("base64url") } export async function genAndStoreAPITokenHash(keyName: string, userId: string): Promise { // The idea here is to not store the API Keys in the database in plaintext. Because if we do that, then // the keys are vulnerable to internal and external database breaches. Instead, we store the hash // of the key in the database. When a user wants to use the key, they send the plaintext key to the server, // which hashes it and compares it to the hash in the database. If they match, then the key is valid. // The reason to not use bcrypt is that bcrypt is designed to be slow, which is good for passwords, but // not good for API keys. Also with salting, one can't simply search for a key in the DB. // Instead, we use SHA-384, which is designed to be fast. Rainbow table attacks are mitigated by using a long // random key, for which rainbow tables become unfeasible and the attacker would have to brute force the key. // The reason to use SHA-384 instead of SHA-256 is that SHA-384 has a longer output, which makes it much harder // to brute force. const token: string = await generateRandomAPIToken() const hashedToken = await hashToken(token) await prisma.cf_api_keys.create({ data: { key: hashedToken, user_id: userId, suffix: token.slice(-4), name: keyName, }, }) return token } export async function userForAPIKey(key: string): Promise { // TODO: Add a rate limiter to prevent brute force attacks. const hashedToken = await hashToken(key) try { const result = await prisma.cf_api_keys.update({ where: { key: hashedToken, }, data: { last_used: new Date(), }, }) if (result == null) { console.log(`No user found for API key ${key}`) return null } else { return result.user_id } } catch (e) { console.log(`Error in userForAPIKey: ${e}`) return null } } export async function deleteAPIKeyById(id: number, userId: string): Promise { return prisma.cf_api_keys.delete({ where: { id, user_id: userId, }, }) }