Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Upgrade to next-auth v5 #1547

Merged
merged 18 commits into from
Feb 8, 2025
Merged
2 changes: 1 addition & 1 deletion apps/web/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ NEXTAUTH_URL=$NEXT_PUBLIC_BASE_URL
SECRET_PASSWORD=abcdef1234567890abcdef1234567890
DATABASE_URL=postgres://postgres:postgres@localhost:5450/rallly
SUPPORT_EMAIL=support@rallly.co
SMTP_HOST=localhost
SMTP_HOST=0.0.0.0
SMTP_PORT=1025
QUICK_CREATE_ENABLED=true
1 change: 1 addition & 0 deletions apps/web/declarations/next-auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module "next-auth" {
}

interface User extends DefaultUser {
id: string;
locale?: string | null;
timeZone?: string | null;
timeFormat?: TimeFormat | null;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/i18next-scanner.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const typescriptTransform = require("i18next-scanner-typescript");

module.exports = {
input: ["src/**/*.{ts,tsx}", "!src/auth.ts"],
input: ["src/**/*.{ts,tsx}", "!src/next-auth*.ts"],
options: {
nsSeparator: false,
defaultNs: "app",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"docker:start": "./scripts/docker-start.sh"
},
"dependencies": {
"@auth/prisma-adapter": "^1.0.3",
"@auth/prisma-adapter": "^2.7.4",
lukevella marked this conversation as resolved.
Show resolved Hide resolved
"@aws-sdk/client-s3": "^3.645.0",
"@aws-sdk/s3-request-presigner": "^3.645.0",
"@hookform/resolvers": "^3.3.1",
Expand Down Expand Up @@ -67,7 +67,7 @@
"lucide-react": "^0.387.0",
"micro": "^10.0.1",
"nanoid": "^5.0.9",
"next-auth": "^4.24.5",
"next-auth": "^5.0.0-beta.25",
"next-i18next": "^13.0.3",
"php-serialize": "^4.1.1",
"postcss": "^8.4.31",
Expand Down
7 changes: 2 additions & 5 deletions apps/web/public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"emailNotAllowed": "This email is not allowed.",
"emailPlaceholder": "jessie.smith@example.com",
"exportToCsv": "Export to CSV",
"forgetMe": "Forget me",
"guest": "Guest",
"ifNeedBe": "If need be",
"location": "Location",
Expand Down Expand Up @@ -199,9 +198,6 @@
"pollStatusFinalized": "Finalized",
"share": "Share",
"noParticipants": "No participants",
"userId": "User ID",
"aboutGuest": "Guest User",
"aboutGuestDescription": "Profile settings are not available for guest users. <0>Sign in</0> to your existing account or <1>create a new account</1> to customize your profile.",
"logoutDescription": "Sign out of your existing session",
"events": "Events",
"inviteParticipantsDescription": "Copy and share the invite link to start gathering responses from your participants.",
Expand Down Expand Up @@ -305,5 +301,6 @@
"registerVerifyDescription": "Check your email for the verification code",
"loginVerifyTitle": "Finish Logging In",
"loginVerifyDescription": "Check your email for the verification code",
"createAccount": "Create Account"
"createAccount": "Create Account",
"loginMagicLinkError": "This link is invalid or expired. Please request a new link."
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function DeleteAccountDialog({
onSuccess() {
posthog?.capture("delete account");
signOut({
callbackUrl: "/login",
redirectTo: "/login",
});
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ import { Button } from "@rallly/ui/button";
import { useMutation } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import React from "react";

import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
import { Skeleton } from "@/components/skeleton";
import { Trans } from "@/components/trans";
import { useTranslation } from "@/i18n/client";
import { trpc } from "@/trpc/client";

type PageProps = { magicLink: string; email: string };

export const LoginPage = ({ magicLink, email }: PageProps) => {
const session = useSession();
const posthog = usePostHog();
const { t } = useTranslation();
const [error, setError] = React.useState<string | null>(null);

const magicLinkFetch = useMutation({
mutationFn: async () => {
const res = await fetch(magicLink);
Expand All @@ -31,9 +36,15 @@ export const LoginPage = ({ magicLink, email }: PageProps) => {
name: updatedSession.user.name,
});
}
router.push(data.url);
} else {
setError(
t("loginMagicLinkError", {
defaultValue:
"This link is invalid or expired. Please request a new link.",
}),
);
}

router.push(data.url);
},
});
const { data } = trpc.user.getByEmail.useQuery({ email });
Expand Down Expand Up @@ -72,6 +83,7 @@ export const LoginPage = ({ magicLink, email }: PageProps) => {
<Trans i18nKey="login" defaults="Login" />
</Button>
</div>
{error && <p className="text-destructive text-sm">{error}</p>}
</div>
</div>
);
Expand Down
22 changes: 14 additions & 8 deletions apps/web/src/app/[locale]/(auth)/login/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@ import { prisma } from "@rallly/database";
import { cookies } from "next/headers";

export async function setVerificationEmail(email: string) {
const count = await prisma.user.count({
const user = await prisma.user.findUnique({
where: {
email,
},
select: {
email: true,
},
});

cookies().set("verification-email", email, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 15 * 60,
});
if (user) {
cookies().set("verification-email", user.email, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 15 * 60,
});
return true;
}

return count > 0;
return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ export function LoginWithEmailForm() {
if (doesExist) {
await signIn("email", {
email: identifier,
callbackUrl: searchParams?.get("callbackUrl") ?? undefined,
redirectTo: searchParams?.get("redirectTo") ?? undefined,
redirect: false,
});
// redirect to verify page with callbackUrl
// redirect to verify page with redirectTo
router.push(
`/login/verify?callbackUrl=${encodeURIComponent(
searchParams?.get("callbackUrl") ?? "",
`/login/verify?redirectTo=${encodeURIComponent(
searchParams?.get("redirectTo") ?? "",
)}`,
);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { Trans } from "@/components/trans";

export async function LoginWithOIDC({
name,
callbackUrl,
redirectTo,
}: {
name: string;
callbackUrl?: string;
redirectTo?: string;
}) {
return (
<Button
onClick={() => {
signIn("oidc", {
callbackUrl,
redirectTo: redirectTo,
});
}}
variant="link"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function SSOImage({ provider }: { provider: string }) {
);
}

if (provider === "azure-ad") {
if (provider === "microsoft-entra-id") {
return (
<Image
src="/static/microsoft.svg"
Expand All @@ -40,11 +40,11 @@ function SSOImage({ provider }: { provider: string }) {
export function SSOProvider({
providerId,
name,
callbackUrl,
redirectTo,
}: {
providerId: string;
name: string;
callbackUrl?: string;
redirectTo?: string;
}) {
const { t } = useTranslation();
return (
Expand All @@ -58,7 +58,7 @@ export function SSOProvider({
key={providerId}
onClick={() => {
signIn(providerId, {
callbackUrl,
redirectTo: redirectTo,
});
}}
>
Expand Down
38 changes: 18 additions & 20 deletions apps/web/src/app/[locale]/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Link from "next/link";
import { Trans } from "react-i18next/TransWithoutContext";

import { getOAuthProviders } from "@/auth";
import { GoogleProvider } from "@/auth/providers/google";
import { MicrosoftProvider } from "@/auth/providers/microsoft";
import { OIDCProvider } from "@/auth/providers/oidc";
import { getTranslation } from "@/i18n/server";

import {
Expand All @@ -22,20 +24,14 @@ export default async function LoginPage({
searchParams,
}: {
searchParams?: {
callbackUrl?: string;
redirectTo?: string;
};
}) {
const { t } = await getTranslation();
const oAuthProviders = getOAuthProviders();

const hasAlternateLoginMethods = oAuthProviders.length > 0;

const oidcProvider = oAuthProviders.find(
(provider) => provider.id === "oidc",
);
const socialProviders = oAuthProviders.filter(
(provider) => provider.id !== "oidc",
);
const oidcProvider = OIDCProvider();
const socialProviders = [GoogleProvider(), MicrosoftProvider()];
const hasAlternateLoginMethods = socialProviders.length > 0 || !!oidcProvider;

return (
<AuthPageContainer>
Expand All @@ -58,19 +54,21 @@ export default async function LoginPage({
{oidcProvider ? (
<LoginWithOIDC
name={oidcProvider.name}
callbackUrl={searchParams?.callbackUrl}
redirectTo={searchParams?.redirectTo}
/>
) : null}
{socialProviders ? (
<div className="grid gap-4">
{socialProviders.map((provider) => (
<SSOProvider
key={provider.id}
providerId={provider.id}
name={provider.name}
callbackUrl={searchParams?.callbackUrl}
/>
))}
{socialProviders.map((provider) =>
provider ? (
<SSOProvider
key={provider.id}
providerId={provider.id}
name={provider.options?.name || provider.name}
redirectTo={searchParams?.redirectTo}
/>
) : null,
)}
</div>
) : null}
</AuthPageContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function OTPForm({ email }: { email: string }) {
message: t("wrongVerificationCode"),
});
} else {
window.location.href = searchParams?.get("callbackUrl") ?? "/";
window.location.href = searchParams?.get("redirectTo") ?? "/";
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function OTPForm({ token }: { token: string }) {

signIn("registration-token", {
token,
callbackUrl: searchParams?.get("callbackUrl") ?? "/",
redirectTo: searchParams?.get("redirectTo") ?? "/",
});
});

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import React from "react";

import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
import { Providers } from "@/app/providers";
import { getServerSession } from "@/auth";
import { SessionProvider } from "@/auth/session-provider";
import { auth } from "@/next-auth";

const inter = Inter({
subsets: ["latin"],
Expand All @@ -30,7 +30,7 @@ export default async function Root({
children: React.ReactNode;
params: { locale: string };
}) {
const session = await getServerSession();
const session = await auth();

return (
<html lang={locale} className={inter.className}>
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { withPosthog } from "@rallly/posthog/server";

import { handlers } from "@/next-auth";

export const GET = withPosthog(handlers.GET);
export const POST = withPosthog(handlers.POST);
4 changes: 2 additions & 2 deletions apps/web/src/app/api/notifications/unsubscribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cookies } from "next/headers";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { getServerSession } from "@/auth";
import { auth } from "@/next-auth";
import type { DisableNotificationsPayload } from "@/trpc/types";
import { decryptToken } from "@/utils/session";

Expand All @@ -14,7 +14,7 @@ export const GET = async (req: NextRequest) => {
return NextResponse.redirect(new URL("/login", req.url));
lukevella marked this conversation as resolved.
Show resolved Hide resolved
}

const session = await getServerSession();
const session = await auth();

if (!session || !session.user?.email) {
return NextResponse.redirect(new URL("/login", req.url));
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/app/api/stripe/checkout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { z } from "zod";

import { getServerSession } from "@/auth";
import { auth } from "@/next-auth";

const inputSchema = z.object({
period: z.enum(["monthly", "yearly"]).optional(),
Expand All @@ -14,7 +14,7 @@ const inputSchema = z.object({
});

export async function POST(request: NextRequest) {
const userSession = await getServerSession();
const userSession = await auth();
const formData = await request.formData();
const { period = "monthly", return_path } = inputSchema.parse(
Object.fromEntries(formData.entries()),
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/app/api/stripe/portal/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as Sentry from "@sentry/nextjs";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { getServerSession } from "@/auth";
import { auth } from "@/next-auth";

export async function GET(request: NextRequest) {
const sessionId = request.nextUrl.searchParams.get("session_id");
Expand All @@ -32,7 +32,7 @@ export async function GET(request: NextRequest) {
);
}
} else {
const userSession = await getServerSession();
const userSession = await auth();
if (!userSession?.user || userSession.user.email === null) {
Sentry.captureException(new Error("User not logged in"));
return NextResponse.json(
Expand Down
Loading
Loading