Skip to content

Commit 88b9de5

Browse files
committed
♻️ Upgrade to latest next-auth
1 parent d7b518f commit 88b9de5

File tree

29 files changed

+386
-231
lines changed

29 files changed

+386
-231
lines changed

apps/web/declarations/next-auth.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ declare module "next-auth" {
2020
}
2121

2222
interface User extends DefaultUser {
23+
id: string;
2324
locale?: string | null;
2425
timeZone?: string | null;
2526
timeFormat?: TimeFormat | null;

apps/web/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"docker:start": "./scripts/docker-start.sh"
1919
},
2020
"dependencies": {
21-
"@auth/prisma-adapter": "^1.0.3",
21+
"@auth/prisma-adapter": "^2.7.4",
2222
"@aws-sdk/client-s3": "^3.645.0",
2323
"@aws-sdk/s3-request-presigner": "^3.645.0",
2424
"@hookform/resolvers": "^3.3.1",
@@ -67,7 +67,7 @@
6767
"lucide-react": "^0.387.0",
6868
"micro": "^10.0.1",
6969
"nanoid": "^5.0.9",
70-
"next-auth": "^4.24.5",
70+
"next-auth": "^5.0.0-beta.25",
7171
"next-i18next": "^13.0.3",
7272
"php-serialize": "^4.1.1",
7373
"postcss": "^8.4.31",

apps/web/src/app/[locale]/(admin)/settings/profile/delete-account-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function DeleteAccountDialog({
3838
onSuccess() {
3939
posthog?.capture("delete account");
4040
signOut({
41-
callbackUrl: "/login",
41+
redirectTo: "/login",
4242
});
4343
},
4444
});

apps/web/src/app/[locale]/(auth)/login/components/login-email-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ export function LoginWithEmailForm() {
5353
if (doesExist) {
5454
await signIn("email", {
5555
email: identifier,
56-
callbackUrl: searchParams?.get("callbackUrl") ?? undefined,
56+
redirectTo: searchParams?.get("callbackUrl") ?? undefined,
5757
redirect: false,
5858
});
5959
// redirect to verify page with callbackUrl
6060
router.push(
6161
`/login/verify?callbackUrl=${encodeURIComponent(
62-
searchParams?.get("callbackUrl") ?? "",
62+
searchParams?.get("callbac`kUrl") ?? "",
6363
)}`,
6464
);
6565
} else {

apps/web/src/app/[locale]/(auth)/login/components/login-with-oidc.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function LoginWithOIDC({
1515
<Button
1616
onClick={() => {
1717
signIn("oidc", {
18-
callbackUrl,
18+
redirectTo: callbackUrl,
1919
});
2020
}}
2121
variant="link"

apps/web/src/app/[locale]/(auth)/login/components/sso-provider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function SSOImage({ provider }: { provider: string }) {
1515
);
1616
}
1717

18-
if (provider === "azure-ad") {
18+
if (provider === "microsoft-entra-id") {
1919
return (
2020
<Image
2121
src="/static/microsoft.svg"
@@ -58,7 +58,7 @@ export function SSOProvider({
5858
key={providerId}
5959
onClick={() => {
6060
signIn(providerId, {
61-
callbackUrl,
61+
redirectTo: callbackUrl,
6262
});
6363
}}
6464
>

apps/web/src/app/[locale]/(auth)/login/page.tsx

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import Link from "next/link";
22
import { Trans } from "react-i18next/TransWithoutContext";
33

4-
import { getOAuthProviders } from "@/auth";
4+
import { getOptionalProviders } from "@/auth/get-optional-providers";
5+
import { GoogleProvider } from "@/auth/providers/google";
6+
import { MicrosoftProvider } from "@/auth/providers/microsoft";
7+
import { OIDCProvider } from "@/auth/providers/oidc";
58
import { getTranslation } from "@/i18n/server";
69

710
import {
@@ -26,16 +29,15 @@ export default async function LoginPage({
2629
};
2730
}) {
2831
const { t } = await getTranslation();
29-
const oAuthProviders = getOAuthProviders();
32+
const oAuthProviders = getOptionalProviders().map((provider) => ({
33+
id: provider.options,
34+
name: provider.name,
35+
}));
3036

3137
const hasAlternateLoginMethods = oAuthProviders.length > 0;
3238

33-
const oidcProvider = oAuthProviders.find(
34-
(provider) => provider.id === "oidc",
35-
);
36-
const socialProviders = oAuthProviders.filter(
37-
(provider) => provider.id !== "oidc",
38-
);
39+
const oidcProvider = OIDCProvider();
40+
const socialProviders = [GoogleProvider(), MicrosoftProvider()];
3941

4042
return (
4143
<AuthPageContainer>
@@ -63,14 +65,16 @@ export default async function LoginPage({
6365
) : null}
6466
{socialProviders ? (
6567
<div className="grid gap-4">
66-
{socialProviders.map((provider) => (
67-
<SSOProvider
68-
key={provider.id}
69-
providerId={provider.id}
70-
name={provider.name}
71-
callbackUrl={searchParams?.callbackUrl}
72-
/>
73-
))}
68+
{socialProviders.map((provider) =>
69+
provider ? (
70+
<SSOProvider
71+
key={provider.id}
72+
providerId={provider.id}
73+
name={provider.options?.name || provider.name}
74+
callbackUrl={searchParams?.callbackUrl}
75+
/>
76+
) : null,
77+
)}
7478
</div>
7579
) : null}
7680
</AuthPageContent>

apps/web/src/app/[locale]/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import React from "react";
88

99
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
1010
import { Providers } from "@/app/providers";
11-
import { getServerSession } from "@/auth";
1211
import { SessionProvider } from "@/auth/session-provider";
12+
import { auth } from "@/next-auth";
1313

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

3535
return (
3636
<html lang={locale} className={inter.className}>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { withPosthog } from "@rallly/posthog/server";
2+
3+
import { handlers } from "@/next-auth";
4+
5+
export const GET = withPosthog(handlers.GET);
6+
export const POST = withPosthog(handlers.POST);

apps/web/src/app/api/notifications/unsubscribe/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cookies } from "next/headers";
33
import type { NextRequest } from "next/server";
44
import { NextResponse } from "next/server";
55

6-
import { getServerSession } from "@/auth";
6+
import { auth } from "@/next-auth";
77
import type { DisableNotificationsPayload } from "@/trpc/types";
88
import { decryptToken } from "@/utils/session";
99

@@ -14,7 +14,7 @@ export const GET = async (req: NextRequest) => {
1414
return NextResponse.redirect(new URL("/login", req.url));
1515
}
1616

17-
const session = await getServerSession();
17+
const session = await auth();
1818

1919
if (!session || !session.user?.email) {
2020
return NextResponse.redirect(new URL("/login", req.url));

apps/web/src/app/api/stripe/checkout/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { NextRequest } from "next/server";
55
import { NextResponse } from "next/server";
66
import { z } from "zod";
77

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

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

1616
export async function POST(request: NextRequest) {
17-
const userSession = await getServerSession();
17+
const userSession = await auth();
1818
const formData = await request.formData();
1919
const { period = "monthly", return_path } = inputSchema.parse(
2020
Object.fromEntries(formData.entries()),

apps/web/src/app/api/stripe/portal/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as Sentry from "@sentry/nextjs";
55
import type { NextRequest } from "next/server";
66
import { NextResponse } from "next/server";
77

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

1010
export async function GET(request: NextRequest) {
1111
const sessionId = request.nextUrl.searchParams.get("session_id");
@@ -32,7 +32,7 @@ export async function GET(request: NextRequest) {
3232
);
3333
}
3434
} else {
35-
const userSession = await getServerSession();
35+
const userSession = await auth();
3636
if (!userSession?.user || userSession.user.email === null) {
3737
Sentry.captureException(new Error("User not logged in"));
3838
return NextResponse.json(

apps/web/src/app/api/trpc/[trpc]/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ipAddress } from "@vercel/functions";
44
import type { NextRequest } from "next/server";
55

66
import { getLocaleFromHeader } from "@/app/guest";
7-
import { getServerSession } from "@/auth";
7+
import { auth } from "@/next-auth";
88
import type { TRPCContext } from "@/trpc/context";
99
import { appRouter } from "@/trpc/routers";
1010
import { getEmailClient } from "@/utils/emails";
@@ -15,7 +15,7 @@ const handler = (req: NextRequest) => {
1515
req,
1616
router: appRouter,
1717
createContext: async () => {
18-
const session = await getServerSession();
18+
const session = await auth();
1919
const locale = await getLocaleFromHeader(req);
2020
const user = session?.user
2121
? {

apps/web/src/app/api/user/verify-email-change/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cookies } from "next/headers";
33
import type { NextRequest } from "next/server";
44
import { NextResponse } from "next/server";
55

6-
import { getServerSession } from "@/auth";
6+
import { auth } from "@/next-auth";
77
import { decryptToken } from "@/utils/session";
88

99
type EmailChangePayload = {
@@ -50,7 +50,7 @@ export const GET = async (request: NextRequest) => {
5050
return NextResponse.json({ error: "No token provided" }, { status: 400 });
5151
}
5252

53-
const session = await getServerSession();
53+
const session = await auth();
5454

5555
if (!session?.user || !session.user.email) {
5656
return NextResponse.redirect(

apps/web/src/auth.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@ import type {
55
NextApiRequest,
66
NextApiResponse,
77
} from "next";
8-
import type { NextAuthOptions } from "next-auth";
9-
import NextAuth, {
10-
getServerSession as getServerSessionWithOptions,
11-
} from "next-auth/next";
12-
import type { Provider } from "next-auth/providers/index";
8+
import type { OAuthProviderType } from "next-auth/providers";
139

14-
import { CustomPrismaAdapter } from "./auth/custom-prisma-adapter";
10+
import { CustomPrismaAdapter } from "./auth/adapters/prisma";
1511
import { mergeGuestsIntoUser } from "./auth/merge-user";
1612
import { EmailProvider } from "./auth/providers/email";
1713
import { GoogleProvider } from "./auth/providers/google";
@@ -23,12 +19,12 @@ import { RegistrationTokenProvider } from "./auth/providers/registration-token";
2319
function getOptionalProviders() {
2420
return [OIDCProvider(), GoogleProvider(), MicrosoftProvider()].filter(
2521
Boolean,
26-
) as Provider[];
22+
) as OAuthProviderType[];
2723
}
2824

2925
const getAuthOptions = (...args: GetServerSessionParams) =>
3026
({
31-
adapter: CustomPrismaAdapter(prisma, {
27+
adapter: CustomPrismaAdapter({
3228
migrateData: async (userId) => {
3329
const session = await getServerSession(...args);
3430
if (session?.user && session.user.email === null) {
@@ -193,11 +189,6 @@ export async function getServerSession(...args: GetServerSessionParams) {
193189
return await getServerSessionWithOptions(...args, getAuthOptions(...args));
194190
}
195191

196-
export async function AuthApiRoute(req: NextApiRequest, res: NextApiResponse) {
197-
const authOptions = getAuthOptions(req, res);
198-
return NextAuth(req, res, authOptions);
199-
}
200-
201192
export const isEmailBlocked = (email: string) => {
202193
if (process.env.ALLOWED_EMAILS) {
203194
const allowedEmails = process.env.ALLOWED_EMAILS.split(",");

apps/web/src/auth/custom-prisma-adapter.ts renamed to apps/web/src/auth/adapters/prisma.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,18 @@
1010
* See: https://github.com/lukevella/rallly/issues/949
1111
*/
1212
import { PrismaAdapter } from "@auth/prisma-adapter";
13-
import type { ExtendedPrismaClient, PrismaClient } from "@rallly/database";
14-
import type { Adapter, AdapterAccount } from "next-auth/adapters";
13+
import { prisma } from "@rallly/database";
14+
import type { Adapter } from "next-auth/adapters";
1515

16-
export function CustomPrismaAdapter(
17-
client: ExtendedPrismaClient,
18-
options: { migrateData: (userId: string) => Promise<void> },
19-
) {
20-
const adapter = PrismaAdapter(client as PrismaClient);
16+
export function CustomPrismaAdapter(options: {
17+
migrateData: (userId: string) => Promise<void>;
18+
}) {
19+
const adapter = PrismaAdapter(prisma);
2120
return {
2221
...adapter,
23-
linkAccount: async (account: AdapterAccount) => {
22+
linkAccount: async (account) => {
2423
await options.migrateData(account.userId);
25-
return (await client.account.create({
24+
return prisma.account.create({
2625
data: {
2726
userId: account.userId,
2827
type: account.type,
@@ -36,7 +35,7 @@ export function CustomPrismaAdapter(
3635
scope: account.scope as string,
3736
session_state: account.session_state as string,
3837
},
39-
})) as AdapterAccount;
38+
});
4039
},
41-
} satisfies Adapter;
40+
} as Adapter;
4241
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Provider } from "next-auth/providers/index";
2+
3+
import { GoogleProvider } from "./providers/google";
4+
import { MicrosoftProvider } from "./providers/microsoft";
5+
import { OIDCProvider } from "./providers/oidc";
6+
7+
export function getOptionalProviders() {
8+
return [OIDCProvider(), GoogleProvider(), MicrosoftProvider()].filter(
9+
Boolean,
10+
) as Provider[];
11+
}

apps/web/src/auth/is-email-blocked.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const isEmailBlocked = (email: string) => {
2+
if (process.env.ALLOWED_EMAILS) {
3+
const allowedEmails = process.env.ALLOWED_EMAILS.split(",");
4+
// Check whether the email matches enough of the patterns specified in ALLOWED_EMAILS
5+
const isAllowed = allowedEmails.some((allowedEmail) => {
6+
const regex = new RegExp(
7+
`^${allowedEmail
8+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
9+
.replaceAll(/[*]/g, ".*")}$`,
10+
);
11+
return regex.test(email);
12+
});
13+
14+
if (!isAllowed) {
15+
return true;
16+
}
17+
}
18+
return false;
19+
};

apps/web/src/auth/providers/email.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { prisma } from "@rallly/database";
22
import { absoluteUrl } from "@rallly/utils/absolute-url";
33
import { generateOtp } from "@rallly/utils/nanoid";
4-
import BaseEmailProvider from "next-auth/providers/email";
4+
import NodemailerProvider from "next-auth/providers/nodemailer";
55

66
import { getEmailClient } from "@/utils/emails";
77

8-
export const EmailProvider = BaseEmailProvider({
9-
server: "",
8+
export const EmailProvider = NodemailerProvider({
9+
server: "none", // This value is required even though we don't need it
1010
from: process.env.NOREPLY_EMAIL,
11+
id: "email",
1112
generateVerificationToken() {
1213
return generateOtp();
1314
},

0 commit comments

Comments
 (0)