diff --git a/apps/web/tests/helpers/next-auth-v4.ts b/apps/web/tests/helpers/next-auth-v4.ts new file mode 100644 index 000000000..a2fbdd59c --- /dev/null +++ b/apps/web/tests/helpers/next-auth-v4.ts @@ -0,0 +1,37 @@ +import hkdf from "@panva/hkdf"; +import { nanoid } from "@rallly/utils/nanoid"; +import { EncryptJWT } from "jose"; +import type { JWT } from "next-auth/jwt"; + +const now = () => (Date.now() / 1000) | 0; +export async function getDerivedEncryptionKey( + keyMaterial: string | Buffer, + salt: string, +) { + return await hkdf( + "sha256", + keyMaterial, + salt, + `NextAuth.js Generated Encryption Key${salt ? ` (${salt})` : ""}`, + 32, + ); +} + +interface JWTEncodeParams { + token?: JWT; + salt?: string; + secret: string | Buffer; + maxAge?: number; +} + +export async function encode(params: JWTEncodeParams) { + /** @note empty `salt` means a session token. See {@link JWTEncodeParams.salt}. */ + const { token = {}, secret, maxAge = 30 * 24 * 60 * 60, salt = "" } = params; + const encryptionSecret = await getDerivedEncryptionKey(secret, salt); + return await new EncryptJWT(token) + .setProtectedHeader({ alg: "dir", enc: "A256GCM" }) + .setIssuedAt() + .setExpirationTime(now() + maxAge) + .setJti(nanoid()) + .encrypt(encryptionSecret); +} diff --git a/apps/web/tests/next-auth-migration.spec.ts b/apps/web/tests/next-auth-migration.spec.ts new file mode 100644 index 000000000..49558560e --- /dev/null +++ b/apps/web/tests/next-auth-migration.spec.ts @@ -0,0 +1,53 @@ +import { expect, test } from "@playwright/test"; +import { prisma } from "@rallly/database"; +import { nanoid } from "@rallly/utils/nanoid"; + +import { encode } from "./helpers/next-auth-v4"; + +const legacyGuestId = "user-1234"; + +test.describe.serial(() => { + test.beforeAll(async () => { + await prisma.poll.create({ + data: { + id: "legacy-guest-poll", + title: "Test Poll", + adminUrlId: nanoid(), + participantUrlId: nanoid(), + guestId: legacyGuestId, + }, + }); + }); + test.afterAll(async () => { + await prisma.poll.delete({ + where: { + id: "legacy-guest-poll", + }, + }); + }); + + test("should see poll on login page", async ({ page }) => { + const context = page.context(); + const token = await encode({ + token: { + sub: legacyGuestId, + }, + secret: process.env.SECRET_PASSWORD, + }); + + // set cookie to simulate legacy guest + await context.addCookies([ + { + name: "next-auth.session-token", + value: token, + httpOnly: true, + secure: false, + sameSite: "Lax", + path: "/", + domain: "localhost", + }, + ]); + await page.goto("/login"); + await expect(page.getByText("Test Poll")).toBeVisible(); + }); +});