codeflash-internal/js/common/token-functions.ts
2023-12-10 17:40:07 -08:00

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,
},
})
}