diff --git a/biome.json b/biome.json index 71e530ec..df138bcb 100644 --- a/biome.json +++ b/biome.json @@ -59,6 +59,6 @@ "ignore": ["node_modules", "dist", "glitch", "glitch-dev"] }, "javascript": { - "globals": ["Bun", "HTMLRewriter"] + "globals": ["Bun", "HTMLRewriter", "BufferEncoding"] } } diff --git a/packages/database-interface/user.ts b/packages/database-interface/user.ts index 246ec50d..0d3f4a0d 100644 --- a/packages/database-interface/user.ts +++ b/packages/database-interface/user.ts @@ -1,6 +1,6 @@ -import { randomBytes } from "node:crypto"; import { idValidator } from "@/api"; import { getBestContentType, urlToContentFormat } from "@/content_types"; +import { randomString } from "@/math"; import { addUserToMeilisearch } from "@/meilisearch"; import { proxyUrl } from "@/response"; import { EntityValidator } from "@lysand-org/federation"; @@ -190,7 +190,7 @@ export class User extends BaseInterface { } async resetPassword() { - const resetToken = await randomBytes(32).toString("hex"); + const resetToken = randomString(32, "hex"); await this.update({ passwordResetToken: resetToken, diff --git a/server/api/api/auth/login/index.test.ts b/server/api/api/auth/login/index.test.ts index 643003db..99f5fab5 100644 --- a/server/api/api/auth/login/index.test.ts +++ b/server/api/api/auth/login/index.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { randomBytes } from "node:crypto"; +import { randomString } from "@/math"; import { eq } from "drizzle-orm"; import { db } from "~/drizzle/db"; import { Applications } from "~/drizzle/schema"; @@ -15,7 +15,7 @@ const application = ( .insert(Applications) .values({ name: "Test Application", - clientId: randomBytes(32).toString("hex"), + clientId: randomString(32, "hex"), secret: "test", redirectUri: "https://example.com", scopes: "read write", diff --git a/server/api/api/auth/mastodon-login/index.ts b/server/api/api/auth/mastodon-login/index.ts index 3bc4c11e..549ae944 100644 --- a/server/api/api/auth/mastodon-login/index.ts +++ b/server/api/api/auth/mastodon-login/index.ts @@ -1,5 +1,5 @@ -import { randomBytes } from "node:crypto"; import { applyConfig, handleZodError } from "@/api"; +import { randomString } from "@/math"; import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; @@ -81,8 +81,8 @@ export default (app: Hono) => }); } - const code = randomBytes(32).toString("hex"); - const accessToken = randomBytes(64).toString("base64url"); + const code = randomString(32, "hex"); + const accessToken = randomString(64, "base64url"); await db.insert(Tokens).values({ accessToken, diff --git a/server/api/api/auth/reset/index.test.ts b/server/api/api/auth/reset/index.test.ts index c55895cd..4e8c65cb 100644 --- a/server/api/api/auth/reset/index.test.ts +++ b/server/api/api/auth/reset/index.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { randomBytes } from "node:crypto"; +import { randomString } from "@/math"; import { eq } from "drizzle-orm"; import { db } from "~/drizzle/db"; import { Applications } from "~/drizzle/schema"; @@ -8,8 +8,8 @@ import { getTestUsers, sendTestRequest } from "~/tests/utils"; import { meta } from "./index"; const { users, deleteUsers, passwords } = await getTestUsers(1); -const token = randomBytes(32).toString("hex"); -const newPassword = randomBytes(16).toString("hex"); +const token = randomString(32, "hex"); +const newPassword = randomString(16, "hex"); // Create application const application = ( @@ -17,7 +17,7 @@ const application = ( .insert(Applications) .values({ name: "Test Application", - clientId: randomBytes(32).toString("hex"), + clientId: randomString(32, "hex"), secret: "test", redirectUri: "https://example.com", scopes: "read write", diff --git a/server/api/api/v1/accounts/index.test.ts b/server/api/api/v1/accounts/index.test.ts index 259e37c2..ff73c326 100644 --- a/server/api/api/v1/accounts/index.test.ts +++ b/server/api/api/v1/accounts/index.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test"; -import { randomBytes } from "node:crypto"; +import { randomString } from "@/math"; import { config } from "config-manager"; import { eq } from "drizzle-orm"; import { db } from "~/drizzle/db"; @@ -7,8 +7,8 @@ import { Users } from "~/drizzle/schema"; import { sendTestRequest } from "~/tests/utils"; import { meta } from "./index"; -const username = randomBytes(10).toString("hex"); -const username2 = randomBytes(10).toString("hex"); +const username = randomString(10, "hex"); +const username2 = randomString(10, "hex"); afterEach(async () => { await db.delete(Users).where(eq(Users.username, username)); diff --git a/server/api/api/v1/apps/index.ts b/server/api/api/v1/apps/index.ts index 92e7615e..0d791347 100644 --- a/server/api/api/v1/apps/index.ts +++ b/server/api/api/v1/apps/index.ts @@ -1,5 +1,5 @@ -import { randomBytes } from "node:crypto"; import { applyConfig, handleZodError, jsonOrForm } from "@/api"; +import { randomString } from "@/math"; import { jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; @@ -54,8 +54,8 @@ export default (app: Hono) => redirectUri: decodeURIComponent(redirect_uris) || "", scopes: scopes || "read", website: website || null, - clientId: randomBytes(32).toString("base64url"), - secret: randomBytes(64).toString("base64url"), + clientId: randomString(32, "base64url"), + secret: randomString(64, "base64url"), }) .returning() )[0]; diff --git a/server/api/api/v1/sso/index.ts b/server/api/api/v1/sso/index.ts index da320e61..64ce5c92 100644 --- a/server/api/api/v1/sso/index.ts +++ b/server/api/api/v1/sso/index.ts @@ -1,6 +1,6 @@ -import { randomBytes } from "node:crypto"; import { applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; import { oauthRedirectUri } from "@/constants"; +import { randomString } from "@/math"; import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; @@ -134,9 +134,7 @@ export default (app: Hono) => await db .insert(Applications) .values({ - clientId: - user.id + - randomBytes(32).toString("base64url"), + clientId: user.id + randomString(32, "base64"), name: "Lysand", redirectUri: `${oauthRedirectUri(issuerId)}`, scopes: "openid profile email", diff --git a/server/api/oauth/authorize/index.ts b/server/api/oauth/authorize/index.ts index 86f48a9a..7c635d97 100644 --- a/server/api/oauth/authorize/index.ts +++ b/server/api/oauth/authorize/index.ts @@ -1,5 +1,5 @@ -import { randomBytes } from "node:crypto"; import { applyConfig, handleZodError, jsonOrForm } from "@/api"; +import { randomString } from "@/math"; import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; @@ -240,7 +240,7 @@ export default (app: Hono) => } // Generate tokens - const code = randomBytes(256).toString("base64url"); + const code = randomString(256, "base64url"); // Handle the requested scopes let idTokenPayload = {}; @@ -287,7 +287,7 @@ export default (app: Hono) => .sign(privateKey); await db.insert(Tokens).values({ - accessToken: randomBytes(64).toString("base64url"), + accessToken: randomString(64, "base64url"), code: code, scope: scope ?? application.scopes, tokenType: TokenType.Bearer, diff --git a/server/api/oauth/sso/:issuer/callback/index.ts b/server/api/oauth/sso/:issuer/callback/index.ts index e2d045f2..cc75f2aa 100644 --- a/server/api/oauth/sso/:issuer/callback/index.ts +++ b/server/api/oauth/sso/:issuer/callback/index.ts @@ -1,5 +1,5 @@ -import { randomBytes } from "node:crypto"; import { applyConfig, handleZodError } from "@/api"; +import { randomString } from "@/math"; import { errorResponse, response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; @@ -174,10 +174,10 @@ export default (app: Hono) => return errorResponse("Application not found", 500); } - const code = randomBytes(32).toString("hex"); + const code = randomString(32, "hex"); await db.insert(Tokens).values({ - accessToken: randomBytes(64).toString("base64url"), + accessToken: randomString(64, "base64url"), code: code, scope: flow.application.scopes, tokenType: TokenType.Bearer, diff --git a/tests/utils.ts b/tests/utils.ts index 7ad390b5..95cc3198 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,5 +1,5 @@ -import { randomBytes } from "node:crypto"; import { consoleLogger } from "@/loggers"; +import { randomString } from "@/math"; import { asc, inArray, like } from "drizzle-orm"; import type { Status } from "~/database/entities/status"; import { db } from "~/drizzle/db"; @@ -35,11 +35,11 @@ export const getTestUsers = async (count: number) => { const passwords: string[] = []; for (let i = 0; i < count; i++) { - const password = randomBytes(32).toString("hex"); + const password = randomString(32, "hex"); const user = await User.fromDataLocal({ - username: `test-${randomBytes(32).toString("hex")}`, - email: `${randomBytes(32).toString("hex")}@test.com`, + username: `test-${randomString(32, "hex")}`, + email: `${randomString(32, "hex")}@test.com`, password, }); @@ -55,11 +55,11 @@ export const getTestUsers = async (count: number) => { .insert(Tokens) .values( users.map((u) => ({ - accessToken: randomBytes(32).toString("hex"), + accessToken: randomString(32, "hex"), tokenType: "bearer", userId: u.id, applicationId: null, - code: randomBytes(32).toString("hex"), + code: randomString(32, "hex"), scope: "read write follow push", })), ) @@ -89,7 +89,7 @@ export const getTestStatuses = async ( for (let i = 0; i < count; i++) { const newStatus = await Note.insert({ - content: `${i} ${randomBytes(32).toString("hex")}`, + content: `${i} ${randomString(32, "hex")}`, authorId: user.id, sensitive: false, updatedAt: new Date().toISOString(), diff --git a/utils/math.ts b/utils/math.ts new file mode 100644 index 00000000..3d7ba601 --- /dev/null +++ b/utils/math.ts @@ -0,0 +1,4 @@ +export const randomString = (length: number, encoding?: BufferEncoding) => + Buffer.from(crypto.getRandomValues(new Uint8Array(length))).toString( + encoding, + );