Skip to content

Commit ab537c6

Browse files
authored
Merge pull request #10 from Hombre2014/authorization-bypass-fix
fix: authorization bypass in Next.js bump it to 14.2.25
2 parents 6d6a54a + aa50ada commit ab537c6

File tree

6 files changed

+2908
-1503
lines changed

6 files changed

+2908
-1503
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ yarn-error.log*
3535
# typescript
3636
*.tsbuildinfo
3737
next-env.d.ts
38+
.qodo

actions/settings.ts

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
1-
"use server";
2-
3-
import * as z from "zod";
4-
import bcrypt from "bcryptjs";
5-
6-
import { update } from "@/auth";
7-
import { db } from "@/lib/db";
8-
import { SettingsSchema } from "@/schemas";
9-
import { getUserByEmail, getUserById } from "@/data/user";
10-
import { currentUser } from "@/lib/auth";
11-
import { generateVerificationToken } from "@/lib/tokens";
12-
import { sendVerificationEmail } from "@/lib/mail";
13-
14-
export const settings = async (
15-
values: z.infer<typeof SettingsSchema>
16-
) => {
1+
'use server';
2+
3+
import * as z from 'zod';
4+
import bcrypt from 'bcryptjs';
5+
6+
import { auth } from '@/auth'; // Import the new `auth()` function
7+
import { db } from '@/lib/db';
8+
import { SettingsSchema } from '@/schemas';
9+
import { getUserByEmail, getUserById } from '@/data/user';
10+
import { currentUser } from '@/lib/auth';
11+
import { generateVerificationToken } from '@/lib/tokens';
12+
import { sendVerificationEmail } from '@/lib/mail';
13+
14+
export const settings = async (values: z.infer<typeof SettingsSchema>) => {
1715
const user = await currentUser();
1816

1917
if (!user) {
20-
return { error: "Unauthorized" }
18+
return { error: 'Unauthorized' };
2119
}
2220

23-
const dbUser = await getUserById(user.id);
21+
const dbUser = await getUserById(user.id!);
2422

2523
if (!dbUser) {
26-
return { error: "Unauthorized" }
24+
return { error: 'Unauthorized' };
2725
}
2826

2927
if (user.isOAuth) {
@@ -37,34 +35,29 @@ export const settings = async (
3735
const existingUser = await getUserByEmail(values.email);
3836

3937
if (existingUser && existingUser.id !== user.id) {
40-
return { error: "Email already in use!" }
38+
return { error: 'Email already in use!' };
4139
}
4240

43-
const verificationToken = await generateVerificationToken(
44-
values.email
45-
);
41+
const verificationToken = await generateVerificationToken(values.email);
4642
await sendVerificationEmail(
4743
verificationToken.email,
48-
verificationToken.token,
44+
verificationToken.token
4945
);
5046

51-
return { success: "Verification email sent!" };
47+
return { success: 'Verification email sent!' };
5248
}
5349

5450
if (values.password && values.newPassword && dbUser.password) {
5551
const passwordsMatch = await bcrypt.compare(
5652
values.password,
57-
dbUser.password,
53+
dbUser.password
5854
);
5955

6056
if (!passwordsMatch) {
61-
return { error: "Incorrect password!" };
57+
return { error: 'Incorrect password!' };
6258
}
6359

64-
const hashedPassword = await bcrypt.hash(
65-
values.newPassword,
66-
10,
67-
);
60+
const hashedPassword = await bcrypt.hash(values.newPassword, 10);
6861
values.password = hashedPassword;
6962
values.newPassword = undefined;
7063
}
@@ -73,17 +66,40 @@ export const settings = async (
7366
where: { id: dbUser.id },
7467
data: {
7568
...values,
76-
}
69+
},
7770
});
7871

79-
update({
80-
user: {
72+
// Custom session update logic using `auth()`
73+
const session = await auth();
74+
if (session && session.user) {
75+
session.user = {
76+
...session.user,
8177
name: updatedUser.name,
8278
email: updatedUser.email,
8379
isTwoFactorEnabled: updatedUser.isTwoFactorEnabled,
8480
role: updatedUser.role,
85-
}
86-
});
81+
};
82+
}
83+
84+
return { success: 'Settings Updated!' };
85+
};
86+
87+
{
88+
/*
89+
Since NextAuth no longer provides an update function, you can use the getSession and useSession methods from next-auth to manually refresh or update the session after making changes to the user data.
90+
91+
Explanation of Changes:
92+
93+
Custom Session Update Logic:
94+
95+
The getSession method from next-auth/react is used to fetch the current session.
96+
The session's user object is updated with the new user data (e.g., name, email, isTwoFactorEnabled, role).
97+
Removed update Import:
98+
99+
The update function was removed from the imports since it no longer exists in auth.ts.
100+
Preserved Existing Logic:
101+
102+
The rest of the logic for email verification, password updates, and database updates remains unchanged.
87103
88-
return { success: "Settings Updated!" }
89-
}
104+
*/
105+
}

auth.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,58 @@
1-
import NextAuth from "next-auth"
2-
import { UserRole } from "@prisma/client";
3-
import { PrismaAdapter } from "@auth/prisma-adapter";
4-
5-
import { db } from "@/lib/db";
6-
import authConfig from "@/auth.config";
7-
import { getUserById } from "@/data/user";
8-
import { getTwoFactorConfirmationByUserId } from "@/data/two-factor-confirmation";
9-
import { getAccountByUserId } from "./data/account";
10-
11-
export const {
12-
handlers: { GET, POST },
13-
auth,
14-
signIn,
15-
signOut,
16-
update,
17-
} = NextAuth({
1+
import NextAuth from 'next-auth';
2+
import { JWT } from 'next-auth/jwt';
3+
import { UserRole } from '@prisma/client';
4+
import { Account, User, Session } from 'next-auth';
5+
import { PrismaAdapter } from '@auth/prisma-adapter';
6+
7+
import { db } from '@/lib/db';
8+
import authConfig from '@/auth.config';
9+
import { getUserById } from '@/data/user';
10+
import { getAccountByUserId } from './data/account';
11+
import { getTwoFactorConfirmationByUserId } from '@/data/two-factor-confirmation';
12+
13+
export const authOptions: any = {
1814
pages: {
19-
signIn: "/auth/login",
20-
error: "/auth/error",
15+
signIn: '/auth/login',
16+
error: '/auth/error',
2117
},
2218
events: {
23-
async linkAccount({ user }) {
19+
async linkAccount({ user }: { user: User }) {
2420
await db.user.update({
2521
where: { id: user.id },
26-
data: { emailVerified: new Date() }
27-
})
28-
}
22+
data: { emailVerified: new Date() },
23+
});
24+
},
2925
},
3026
callbacks: {
31-
async signIn({ user, account }) {
27+
async signIn({ user, account }: { user: User; account: Account | null }) {
3228
// Allow OAuth without email verification
33-
if (account?.provider !== "credentials") return true;
29+
if (account?.provider !== 'credentials') return true;
30+
31+
if (!user.id) {
32+
return false; // Reject sign-in if user ID is undefined
33+
}
3434

3535
const existingUser = await getUserById(user.id);
3636

3737
// Prevent sign in without email verification
3838
if (!existingUser?.emailVerified) return false;
3939

4040
if (existingUser.isTwoFactorEnabled) {
41-
const twoFactorConfirmation = await getTwoFactorConfirmationByUserId(existingUser.id);
41+
const twoFactorConfirmation = await getTwoFactorConfirmationByUserId(
42+
existingUser.id
43+
);
4244

4345
if (!twoFactorConfirmation) return false;
4446

45-
// Delete two factor confirmation for next sign in
47+
// Delete two-factor confirmation for next sign in
4648
await db.twoFactorConfirmation.delete({
47-
where: { id: twoFactorConfirmation.id }
49+
where: { id: twoFactorConfirmation.id },
4850
});
4951
}
5052

5153
return true;
5254
},
53-
async session({ token, session }) {
55+
async session({ session, token }: { session: Session; token: JWT }) {
5456
if (token.sub && session.user) {
5557
session.user.id = token.sub;
5658
}
@@ -61,26 +63,21 @@ export const {
6163

6264
if (session.user) {
6365
session.user.isTwoFactorEnabled = token.isTwoFactorEnabled as boolean;
64-
}
65-
66-
if (session.user) {
6766
session.user.name = token.name;
6867
session.user.email = token.email;
6968
session.user.isOAuth = token.isOAuth as boolean;
7069
}
7170

7271
return session;
7372
},
74-
async jwt({ token }) {
73+
async jwt({ token }: { token: JWT }) {
7574
if (!token.sub) return token;
7675

7776
const existingUser = await getUserById(token.sub);
7877

7978
if (!existingUser) return token;
8079

81-
const existingAccount = await getAccountByUserId(
82-
existingUser.id
83-
);
80+
const existingAccount = await getAccountByUserId(existingUser.id);
8481

8582
token.isOAuth = !!existingAccount;
8683
token.name = existingUser.name;
@@ -89,9 +86,16 @@ export const {
8986
token.isTwoFactorEnabled = existingUser.isTwoFactorEnabled;
9087

9188
return token;
92-
}
89+
},
9390
},
9491
adapter: PrismaAdapter(db),
95-
session: { strategy: "jwt" },
92+
session: { strategy: 'jwt' },
9693
...authConfig,
97-
});
94+
};
95+
96+
export const {
97+
handlers: { GET, POST },
98+
auth,
99+
signIn,
100+
signOut,
101+
} = NextAuth(authOptions);

middleware.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@ export default auth((req) => {
1919
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
2020

2121
if (isApiAuthRoute) {
22-
return null;
22+
// Do nothing for API auth routes
23+
return;
2324
}
2425

2526
if (isAuthRoute) {
2627
if (isLoggedIn) {
28+
// Redirect logged-in users away from auth routes
2729
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl));
2830
}
29-
return null;
31+
// Allow unauthenticated users to access auth routes
32+
return;
3033
}
3134

3235
if (!isLoggedIn && !isPublicRoute) {
36+
// Redirect unauthenticated users to the login page
3337
let callbackUrl = nextUrl.pathname;
3438
if (nextUrl.search) {
3539
callbackUrl += nextUrl.search;
@@ -42,7 +46,8 @@ export default auth((req) => {
4246
);
4347
}
4448

45-
return null;
49+
// Allow access to public routes or logged-in users
50+
return;
4651
});
4752

4853
// Optionally, don't invoke Middleware on some paths

0 commit comments

Comments
 (0)