From 2493ccf08e4a4e96c6be8f3e5ee80f7db7284dfe Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Sat, 2 Aug 2025 19:32:57 -0700 Subject: feat: Drop support for time bounded invitations --- packages/trpc/email.ts | 4 +- packages/trpc/routers/invites.test.ts | 70 +++++++++++------------------------ packages/trpc/routers/invites.ts | 32 +--------------- 3 files changed, 25 insertions(+), 81 deletions(-) (limited to 'packages/trpc') diff --git a/packages/trpc/email.ts b/packages/trpc/email.ts index e69c4507..edf8ec92 100644 --- a/packages/trpc/email.ts +++ b/packages/trpc/email.ts @@ -100,7 +100,7 @@ export async function sendInviteEmail(

If the button doesn't work, you can copy and paste this link into your browser:

${inviteUrl}

-

This invitation will expire in 7 days.

+

If you weren't expecting this invitation, you can safely ignore this email.

`, @@ -112,7 +112,7 @@ ${inviterName} has invited you to join Karakeep, a powerful bookmarking and cont Accept your invitation by visiting this link: ${inviteUrl} -This invitation will expire in 7 days. + If you weren't expecting this invitation, you can safely ignore this email. `, diff --git a/packages/trpc/routers/invites.test.ts b/packages/trpc/routers/invites.test.ts index f4f048df..5cadbc14 100644 --- a/packages/trpc/routers/invites.test.ts +++ b/packages/trpc/routers/invites.test.ts @@ -53,7 +53,6 @@ describe("Invites Router", () => { }); expect(invite.email).toBe("newuser@test.com"); - expect(invite.expiresAt).toBeDefined(); expect(invite.id).toBeDefined(); // Verify the invite was created in the database @@ -238,7 +237,7 @@ describe("Invites Router", () => { ).rejects.toThrow(/Invite not found/); }); - test("cannot get expired invite", async ({ + test("can get invite by token", async ({ db, unauthedAPICaller, }) => { @@ -255,22 +254,16 @@ describe("Invites Router", () => { email: "newuser@test.com", }); - // Set expiry to past date - const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000); - await db - .update(invites) - .set({ expiresAt: pastDate }) - .where(eq(invites.id, invite.id)); - const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); - await expect(() => - unauthedAPICaller.invites.get({ - token: dbInvite!.token, - }), - ).rejects.toThrow(/Invite has expired/); + const result = await unauthedAPICaller.invites.get({ + token: dbInvite!.token, + }); + + expect(result.email).toBe("newuser@test.com"); + expect(result.expired).toBe(false); }); test("cannot get used invite (deleted)", async ({ @@ -346,7 +339,7 @@ describe("Invites Router", () => { expect(deletedInvite).toBeUndefined(); }); - test("cannot accept expired invite", async ({ + test("can accept valid invite", async ({ db, unauthedAPICaller, }) => { @@ -363,23 +356,18 @@ describe("Invites Router", () => { email: "newuser@test.com", }); - const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000); - await db - .update(invites) - .set({ expiresAt: pastDate }) - .where(eq(invites.id, invite.id)); - const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); - await expect(() => - unauthedAPICaller.invites.accept({ - token: dbInvite!.token, - name: "New User", - password: "newpass123", - }), - ).rejects.toThrow(/This invite has expired/); + const result = await unauthedAPICaller.invites.accept({ + token: dbInvite!.token, + name: "New User", + password: "newpass123", + }); + + expect(result.email).toBe("newuser@test.com"); + expect(result.name).toBe("New User"); }); test("cannot accept used invite (deleted)", async ({ @@ -510,9 +498,7 @@ describe("Invites Router", () => { }); expect(resentInvite.email).toBe("newuser@test.com"); - expect(resentInvite.expiresAt.getTime()).toBeGreaterThan( - originalDbInvite!.expiresAt.getTime(), - ); + expect(resentInvite.id).toBe(originalDbInvite!.id); // Verify token was updated in database const updatedDbInvite = await db.query.invites.findFirst({ @@ -556,7 +542,7 @@ describe("Invites Router", () => { ).rejects.toThrow(/Invite not found/); }); - test("invite expiration is set correctly", async ({ + test("invite creation works without expiration", async ({ db, unauthedAPICaller, }) => { @@ -569,26 +555,12 @@ describe("Invites Router", () => { const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); - const beforeCreate = new Date(); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); - const afterCreate = new Date(); - - // Allow for some timing variance (1 second buffer) - const expectedMinExpiry = new Date( - beforeCreate.getTime() + 7 * 24 * 60 * 60 * 1000 - 1000, - ); - const expectedMaxExpiry = new Date( - afterCreate.getTime() + 7 * 24 * 60 * 60 * 1000 + 1000, - ); - - expect(invite.expiresAt.getTime()).toBeGreaterThanOrEqual( - expectedMinExpiry.getTime(), - ); - expect(invite.expiresAt.getTime()).toBeLessThanOrEqual( - expectedMaxExpiry.getTime(), - ); + + expect(invite.email).toBe("newuser@test.com"); + expect(invite.id).toBeDefined(); }); test("invite includes inviter information", async ({ diff --git a/packages/trpc/routers/invites.ts b/packages/trpc/routers/invites.ts index 0a98f36a..e010fd73 100644 --- a/packages/trpc/routers/invites.ts +++ b/packages/trpc/routers/invites.ts @@ -1,6 +1,6 @@ import { randomBytes } from "crypto"; import { TRPCError } from "@trpc/server"; -import { and, eq, gt } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { z } from "zod"; import { invites, users } from "@karakeep/db/schema"; @@ -35,10 +35,7 @@ export const invitesAppRouter = router({ } const existingInvite = await ctx.db.query.invites.findFirst({ - where: and( - eq(invites.email, input.email), - gt(invites.expiresAt, new Date()), - ), + where: eq(invites.email, input.email), }); if (existingInvite) { @@ -49,14 +46,12 @@ export const invitesAppRouter = router({ } const token = randomBytes(32).toString("hex"); - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const [invite] = await ctx.db .insert(invites) .values({ email: input.email, token, - expiresAt, invitedBy: ctx.user.id, }) .returning(); @@ -76,7 +71,6 @@ export const invitesAppRouter = router({ return { id: invite.id, email: invite.email, - expiresAt: invite.expiresAt, }; }), @@ -88,7 +82,6 @@ export const invitesAppRouter = router({ id: z.string(), email: z.string(), createdAt: z.date(), - expiresAt: z.date(), invitedBy: z.object({ id: z.string(), name: z.string(), @@ -148,16 +141,6 @@ export const invitesAppRouter = router({ }); } - const now = new Date(); - const expired = invite.expiresAt < now; - - if (expired) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Invite has expired", - }); - } - return { email: invite.email, expired: false, @@ -191,14 +174,6 @@ export const invitesAppRouter = router({ }); } - const now = new Date(); - if (invite.expiresAt < now) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "This invite has expired", - }); - } - const existingUser = await ctx.db.query.users.findFirst({ where: eq(users.email, invite.email), }); @@ -273,13 +248,11 @@ export const invitesAppRouter = router({ } const newToken = randomBytes(32).toString("hex"); - const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days await ctx.db .update(invites) .set({ token: newToken, - expiresAt: newExpiresAt, }) .where(eq(invites.id, input.inviteId)); @@ -298,7 +271,6 @@ export const invitesAppRouter = router({ return { id: invite.id, email: invite.email, - expiresAt: newExpiresAt, }; }), }); -- cgit v1.2.3-70-g09d2