mirror of
https://github.com/codeflash-ai/codeflash-internal.git
synced 2026-05-04 18:25:18 +00:00
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
"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<string> {
|
|
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<string> {
|
|
// 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<null | string> {
|
|
// 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<cf_api_keys> {
|
|
return prisma.cf_api_keys.delete({
|
|
where: {
|
|
id,
|
|
user_id: userId,
|
|
},
|
|
})
|
|
}
|