From ebeee1656fa64fa0588a843574260fc12330608b Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 7 Aug 2024 14:11:29 +0530 Subject: [PATCH 1/3] feat: use async --- src/lib/crypto.ts | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index 147f61d7c..08afef046 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -1,4 +1,10 @@ -import { randomBytes, scryptSync, subtle } from "node:crypto"; +import { + type BinaryLike, + randomBytes, + scrypt, + subtle, + timingSafeEqual, +} from "node:crypto"; import { customId } from "@/common/id"; export const createHash = async (key: string) => { @@ -25,18 +31,38 @@ export const initializeAccessToken = ({ }; }; -export const createSecureHash = (secret: string) => { +const scryptAsync = ( + password: BinaryLike, + salt: BinaryLike, + keylen: number, +): Promise => { + return new Promise((resolve, reject) => { + scrypt(password, salt, keylen, (err, derivedKey) => { + if (err) reject(err); + else resolve(derivedKey); + }); + }); +}; + +export const createSecureHash = async (secret: string) => { const data = new TextEncoder().encode(secret); const salt = randomBytes(32).toString("hex"); - const derivedKey = scryptSync(data, salt, 64); + const derivedKey = await scryptAsync(data, salt, 64); return `${salt}:${derivedKey.toString("hex")}`; }; -export const verifySecureHash = (secret: string, hash: string) => { - const data = new TextEncoder().encode(secret); - const [salt, storedHash] = hash.split(":"); - const derivedKey = scryptSync(data, String(salt), 64); +export const verifySecureHash = async (secret: string, hash: string) => { + try { + const data = new TextEncoder().encode(secret); + const [salt, storedHash] = hash.split(":"); + + const derivedKey = await scryptAsync(data, salt ?? "", 64); - return storedHash === derivedKey.toString("hex"); + const derivedKeyBuffer = Buffer.from(derivedKey); + const storedHashBuffer = Buffer.from(storedHash ?? "", "hex"); + return timingSafeEqual(derivedKeyBuffer, storedHashBuffer); + } catch (_error) { + return false; + } }; From 0aa50d33cf5817fa45b45af595cc741034b0e15e Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 7 Aug 2024 14:12:55 +0530 Subject: [PATCH 2/3] fix: early bail out --- src/server/api/middlewares/bearer-token.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/server/api/middlewares/bearer-token.ts b/src/server/api/middlewares/bearer-token.ts index 625576ba8..ccc826a91 100644 --- a/src/server/api/middlewares/bearer-token.ts +++ b/src/server/api/middlewares/bearer-token.ts @@ -1,6 +1,7 @@ import { verifySecureHash } from "@/lib/crypto"; import type { Context } from "hono"; import { createMiddleware } from "hono/factory"; +import { nanoid } from "nanoid"; import { ApiError } from "../error"; export type accessTokenAuthMiddlewareOptions = @@ -48,19 +49,12 @@ async function authenticateWithAccessToken( const accessToken = await findAccessToken(clientId, c); - if (!accessToken) { - throw new ApiError({ - code: "UNAUTHORIZED", - message: "Bearer token is invalid", - }); - } - const isAccessTokenValid = await verifySecureHash( clientSecret, - accessToken.clientSecret, + accessToken?.clientSecret ?? nanoid(), ); - if (!isAccessTokenValid) { + if (!isAccessTokenValid || !accessToken) { throw new ApiError({ code: "UNAUTHORIZED", message: "Bearer token is invalid", From 31779ff6486e2d5746154ed65d44cc2e92ba1a53 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 7 Aug 2024 14:29:08 +0530 Subject: [PATCH 3/3] chore: cleanup --- src/server/api/middlewares/bearer-token.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/api/middlewares/bearer-token.ts b/src/server/api/middlewares/bearer-token.ts index ccc826a91..428d28878 100644 --- a/src/server/api/middlewares/bearer-token.ts +++ b/src/server/api/middlewares/bearer-token.ts @@ -47,11 +47,13 @@ async function authenticateWithAccessToken( }); } + const randomId = nanoid(); + const accessToken = await findAccessToken(clientId, c); const isAccessTokenValid = await verifySecureHash( clientSecret, - accessToken?.clientSecret ?? nanoid(), + accessToken?.clientSecret ?? randomId, ); if (!isAccessTokenValid || !accessToken) {