aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/routers
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-07-12 12:33:23 +0000
committerMohamed Bassem <me@mbassem.com>2025-07-12 12:33:23 +0000
commit8e3013ba96532cab61eb6e5fae2ce30be5e94a57 (patch)
treeb101a0ac7e1218ea2562178e055044f315b0c245 /packages/trpc/routers
parent140311d7419fa2192e5149df8f589c3c3733a399 (diff)
downloadkarakeep-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.ts26
-rw-r--r--packages/trpc/routers/users.test.ts6
-rw-r--r--packages/trpc/routers/users.ts35
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);