From 9e9998ea825b20ebf630be8c9d5d0627e67e5e32 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Wed, 24 Apr 2024 17:48:39 -1000 Subject: [PATCH] refactor(api): :art: Move createLocalUser to a static method of User --- cli.ts | 3 +- database/entities/User.ts | 88 -------------------- packages/database-interface/user.ts | 73 ++++++++++++++++ server/api/api/v1/accounts/[id]/followers.ts | 3 +- server/api/api/v1/accounts/[id]/following.ts | 3 +- server/api/api/v1/accounts/index.ts | 3 +- tests/utils.ts | 5 +- 7 files changed, 79 insertions(+), 99 deletions(-) diff --git a/cli.ts b/cli.ts index d6c425bc..172f6b15 100644 --- a/cli.ts +++ b/cli.ts @@ -13,7 +13,6 @@ import extract from "extract-zip"; import { MediaBackend } from "media-manager"; import { lookup } from "mime-types"; import { getUrl } from "~database/entities/Attachment"; -import { type UserType, createNewLocalUser } from "~database/entities/User"; import { client, db } from "~drizzle/db"; import { Emojis, Notes, OpenIdAccounts, Users } from "~drizzle/schema"; import { Note } from "~packages/database-interface/note"; @@ -129,7 +128,7 @@ const cliBuilder = new CliBuilder([ } // Create user - const newUser = await createNewLocalUser({ + const newUser = await User.fromDataLocal({ email: email, password: password, username: username, diff --git a/database/entities/User.ts b/database/entities/User.ts index e9dd677d..2b0b52dc 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -379,69 +379,6 @@ export const resolveWebFinger = async ( return User.resolve(relevantLink.href); }; -/** - * Fetches the list of followers associated with the actor and updates the user's followers - */ -export const fetchFollowers = () => { - // -}; - -/** - * Creates a new LOCAL user. - * @param data The data for the new user. - * @returns The newly created user. - */ -export const createNewLocalUser = async (data: { - username: string; - display_name?: string; - password: string; - email: string; - bio?: string; - avatar?: string; - header?: string; - admin?: boolean; - skipPasswordHash?: boolean; -}): Promise => { - const keys = await generateUserKeys(); - - const newUser = ( - await db - .insert(Users) - .values({ - username: data.username, - displayName: data.display_name ?? data.username, - password: data.skipPasswordHash - ? data.password - : await Bun.password.hash(data.password), - email: data.email, - note: data.bio ?? "", - avatar: data.avatar ?? config.defaults.avatar, - header: data.header ?? config.defaults.avatar, - isAdmin: data.admin ?? false, - publicKey: keys.public_key, - privateKey: keys.private_key, - updatedAt: new Date().toISOString(), - source: { - language: null, - note: "", - privacy: "public", - sensitive: false, - fields: [], - }, - }) - .returning() - )[0]; - - const finalUser = await User.fromId(newUser.id); - - if (!finalUser) return null; - - // Add to Meilisearch - await addUserToMeilisearch(finalUser); - - return finalUser; -}; - /** * Parses mentions from a list of URIs */ @@ -537,31 +474,6 @@ export const getRelationshipToOtherUser = async ( return foundRelationship; }; -/** - * Generates keys for the user. - */ -export const generateUserKeys = async () => { - const keys = await crypto.subtle.generateKey("Ed25519", true, [ - "sign", - "verify", - ]); - - const privateKey = Buffer.from( - await crypto.subtle.exportKey("pkcs8", keys.privateKey), - ).toString("base64"); - - const publicKey = Buffer.from( - await crypto.subtle.exportKey("spki", keys.publicKey), - ).toString("base64"); - - // Add header, footer and newlines later on - // These keys are base64 encrypted - return { - private_key: privateKey, - public_key: publicKey, - }; -}; - export const followRequestToLysand = ( follower: User, followee: User, diff --git a/packages/database-interface/user.ts b/packages/database-interface/user.ts index d1bb74a9..8a4e2015 100644 --- a/packages/database-interface/user.ts +++ b/packages/database-interface/user.ts @@ -262,6 +262,79 @@ export class User { return this.user.avatar; } + static async generateKeys() { + const keys = await crypto.subtle.generateKey("Ed25519", true, [ + "sign", + "verify", + ]); + + const privateKey = Buffer.from( + await crypto.subtle.exportKey("pkcs8", keys.privateKey), + ).toString("base64"); + + const publicKey = Buffer.from( + await crypto.subtle.exportKey("spki", keys.publicKey), + ).toString("base64"); + + // Add header, footer and newlines later on + // These keys are base64 encrypted + return { + private_key: privateKey, + public_key: publicKey, + }; + } + + static async fromDataLocal(data: { + username: string; + display_name?: string; + password: string; + email: string; + bio?: string; + avatar?: string; + header?: string; + admin?: boolean; + skipPasswordHash?: boolean; + }): Promise { + const keys = await User.generateKeys(); + + const newUser = ( + await db + .insert(Users) + .values({ + username: data.username, + displayName: data.display_name ?? data.username, + password: data.skipPasswordHash + ? data.password + : await Bun.password.hash(data.password), + email: data.email, + note: data.bio ?? "", + avatar: data.avatar ?? config.defaults.avatar, + header: data.header ?? config.defaults.avatar, + isAdmin: data.admin ?? false, + publicKey: keys.public_key, + privateKey: keys.private_key, + updatedAt: new Date().toISOString(), + source: { + language: null, + note: "", + privacy: "public", + sensitive: false, + fields: [], + }, + }) + .returning() + )[0]; + + const finalUser = await User.fromId(newUser.id); + + if (!finalUser) return null; + + // Add to Meilisearch + await addUserToMeilisearch(finalUser); + + return finalUser; + } + /** * Get the user's header in raw URL format * @param config The config to use diff --git a/server/api/api/v1/accounts/[id]/followers.ts b/server/api/api/v1/accounts/[id]/followers.ts index 03207a71..b6d40f92 100644 --- a/server/api/api/v1/accounts/[id]/followers.ts +++ b/server/api/api/v1/accounts/[id]/followers.ts @@ -15,7 +15,7 @@ export const meta = applyConfig({ route: "/api/v1/accounts/:id/followers", auth: { required: false, - oauthPermissions: [], + oauthPermissions: ["read:accounts"], }, }); @@ -36,7 +36,6 @@ export default apiRoute( return errorResponse("Invalid ID, must be of type UUIDv7", 404); } - // TODO: Add pinned const { max_id, min_id, since_id, limit } = extraData.parsedRequest; const otherUser = await User.fromId(id); diff --git a/server/api/api/v1/accounts/[id]/following.ts b/server/api/api/v1/accounts/[id]/following.ts index 5baf5f47..e3a6e49a 100644 --- a/server/api/api/v1/accounts/[id]/following.ts +++ b/server/api/api/v1/accounts/[id]/following.ts @@ -15,7 +15,7 @@ export const meta = applyConfig({ route: "/api/v1/accounts/:id/following", auth: { required: false, - oauthPermissions: [], + oauthPermissions: ["read:accounts"], }, }); @@ -36,7 +36,6 @@ export default apiRoute( return errorResponse("Invalid ID, must be of type UUIDv7", 404); } - // TODO: Add pinned const { max_id, min_id, since_id, limit } = extraData.parsedRequest; const otherUser = await User.fromId(id); diff --git a/server/api/api/v1/accounts/index.ts b/server/api/api/v1/accounts/index.ts index b3872747..053f005a 100644 --- a/server/api/api/v1/accounts/index.ts +++ b/server/api/api/v1/accounts/index.ts @@ -4,7 +4,6 @@ import { tempmailDomains } from "@tempmail"; import { eq } from "drizzle-orm"; import ISO6391 from "iso-639-1"; import { z } from "zod"; -import { createNewLocalUser } from "~database/entities/User"; import { Users } from "~drizzle/schema"; import { User } from "~packages/database-interface/user"; @@ -207,7 +206,7 @@ export default apiRoute( ); } - await createNewLocalUser({ + await User.fromDataLocal({ username: body.username ?? "", password: body.password ?? "", email: body.email ?? "", diff --git a/tests/utils.ts b/tests/utils.ts index 2bbf5260..7bea0159 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,12 +1,11 @@ import { randomBytes } from "node:crypto"; import { asc, inArray, like } from "drizzle-orm"; import type { Status } from "~database/entities/Status"; -import { createNewLocalUser } from "~database/entities/User"; import { db } from "~drizzle/db"; import { Notes, Tokens, Users } from "~drizzle/schema"; import { server } from "~index"; import { Note } from "~packages/database-interface/note"; -import type { User } from "~packages/database-interface/user"; +import { User } from "~packages/database-interface/user"; /** * This allows us to send a test request to the server even when it isnt running * CURRENTLY NOT WORKING, NEEDS TO BE FIXED @@ -33,7 +32,7 @@ export const getTestUsers = async (count: number) => { for (let i = 0; i < count; i++) { const password = randomBytes(32).toString("hex"); - const user = await createNewLocalUser({ + const user = await User.fromDataLocal({ username: `test-${randomBytes(32).toString("hex")}`, email: `${randomBytes(32).toString("hex")}@test.com`, password,