diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-07-12 12:33:23 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-07-12 12:33:23 +0000 |
| commit | 8e3013ba96532cab61eb6e5fae2ce30be5e94a57 (patch) | |
| tree | b101a0ac7e1218ea2562178e055044f315b0c245 /packages/trpc/routers | |
| parent | 140311d7419fa2192e5149df8f589c3c3733a399 (diff) | |
| download | karakeep-8e3013ba96532cab61eb6e5fae2ce30be5e94a57.tar.zst | |
refactor: Move db interactions into the trpc routes
Diffstat (limited to 'packages/trpc/routers')
| -rw-r--r-- | packages/trpc/routers/invites.test.ts | 26 | ||||
| -rw-r--r-- | packages/trpc/routers/users.test.ts | 6 | ||||
| -rw-r--r-- | packages/trpc/routers/users.ts | 35 |
3 files changed, 60 insertions, 7 deletions
diff --git a/packages/trpc/routers/invites.test.ts b/packages/trpc/routers/invites.test.ts index bb1209c4..f4f048df 100644 --- a/packages/trpc/routers/invites.test.ts +++ b/packages/trpc/routers/invites.test.ts @@ -6,6 +6,32 @@ import { invites, users } from "@karakeep/db/schema"; import type { CustomTestContext } from "../testUtils"; import { defaultBeforeEach, getApiCaller } from "../testUtils"; +// Mock server config with email settings +vi.mock("@karakeep/shared/config", async (original) => { + const mod = (await original()) as typeof import("@karakeep/shared/config"); + return { + ...mod, + default: { + ...mod.default, + email: { + smtp: { + host: "test-smtp.example.com", + port: 587, + secure: false, + user: "test@example.com", + password: "test-password", + from: "test@example.com", + }, + }, + }, + }; +}); + +// Mock email functions +vi.mock("../email", () => ({ + sendInviteEmail: vi.fn().mockResolvedValue(undefined), +})); + beforeEach<CustomTestContext>(defaultBeforeEach(false)); describe("Invites Router", () => { diff --git a/packages/trpc/routers/users.test.ts b/packages/trpc/routers/users.test.ts index 03e5d590..1c03f47a 100644 --- a/packages/trpc/routers/users.test.ts +++ b/packages/trpc/routers/users.test.ts @@ -14,7 +14,7 @@ import type { CustomTestContext } from "../testUtils"; import * as emailModule from "../email"; import { defaultBeforeEach, getApiCaller } from "../testUtils"; -// Mock server config with all required properties - MUST be before any imports that use config +// Mock server config with email settings vi.mock("@karakeep/shared/config", async (original) => { const mod = (await original()) as typeof import("@karakeep/shared/config"); return { @@ -516,7 +516,7 @@ describe("User Routes", () => { unauthedAPICaller, }) => { // Create a user first - const user = await unauthedAPICaller.users.create({ + await unauthedAPICaller.users.create({ name: "Test User", email: "reset@test.com", password: "pass1234", @@ -534,7 +534,7 @@ describe("User Routes", () => { expect(emailModule.sendPasswordResetEmail).toHaveBeenCalledWith( "reset@test.com", "Test User", - user.id, + expect.any(String), // token ); }); diff --git a/packages/trpc/routers/users.ts b/packages/trpc/routers/users.ts index 79f06057..8d6db6c7 100644 --- a/packages/trpc/routers/users.ts +++ b/packages/trpc/routers/users.ts @@ -1,3 +1,4 @@ +import { randomBytes } from "crypto"; import { TRPCError } from "@trpc/server"; import { and, count, desc, eq, gte, sql } from "drizzle-orm"; import invariant from "tiny-invariant"; @@ -39,7 +40,21 @@ import { router, } from "../index"; -export async function verifyEmailToken( +async function genEmailVerificationToken(db: Context["db"], email: string) { + const token = randomBytes(10).toString("hex"); + const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours + + // Store verification token + await db.insert(verificationTokens).values({ + identifier: email, + token, + expires, + }); + + return token; +} + +async function verifyEmailToken( db: Context["db"], email: string, token: string, @@ -156,8 +171,9 @@ export async function createUser( }); // Send verification email if required if (serverConfig.auth.emailVerificationRequired) { + const token = await genEmailVerificationToken(ctx.db, input.email); try { - await sendVerificationEmail(input.email, input.name); + await sendVerificationEmail(input.email, input.name, token); } catch (error) { console.error("Failed to send verification email:", error); // Don't fail user creation if email sending fails @@ -671,8 +687,9 @@ export const usersAppRouter = router({ }); } + const token = await genEmailVerificationToken(ctx.db, input.email); try { - await sendVerificationEmail(input.email, user.name); + await sendVerificationEmail(input.email, user.name, token); return { success: true }; } catch (error) { console.error("Failed to send verification email:", error); @@ -718,7 +735,17 @@ export const usersAppRouter = router({ } try { - await sendPasswordResetEmail(input.email, user.name, user.id); + const token = randomBytes(32).toString("hex"); + const expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour + + // Store password reset token + await ctx.db.insert(passwordResetTokens).values({ + userId: user.id, + token, + expires, + }); + + await sendPasswordResetEmail(input.email, user.name, token); return { success: true }; } catch (error) { console.error("Failed to send password reset email:", error); |
