-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.ts
More file actions
93 lines (78 loc) · 2.91 KB
/
auth.ts
File metadata and controls
93 lines (78 loc) · 2.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import NextAuth, {NextAuthResult} from "next-auth";
import {authConfig} from "./auth.config";
import Credentials from "next-auth/providers/credentials";
import {z} from "zod";
import {sql} from "@vercel/postgres";
import type {User} from "./app/lib/definitions";
import bcrypt from "bcrypt";
// Maximum allowed failed login attempts
const MAX_FAILED_ATTEMPTS = 5;
const LOCK_TIME_MINUTES = 15;
async function incrementFailedAttempts(email: string) {
try {
await sql`UPDATE users SET failed_login_attempts = failed_login_attempts + 1 WHERE email = ${email}`;
} catch (error) {
console.error("Failed to increment login attempts:", error);
}
}
async function resetFailedAttempts(email: string) {
try {
await sql`UPDATE users SET failed_login_attempts = 0 WHERE email = ${email}`;
} catch (error) {
console.error("Failed to reset login attempts:", error);
}
}
async function lockUserAccount(email: string) {
const lockUntil = new Date();
lockUntil.setMinutes(lockUntil.getMinutes() + LOCK_TIME_MINUTES);
try {
// Convert Date to ISO string
await sql`UPDATE users SET lock_until = ${lockUntil.toISOString()}, failed_login_attempts = 0 WHERE email = ${email}`;
} catch (error) {
console.error("Failed to lock account:", error);
}
}
async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
return user.rows[0];
} catch (error) {
console.error("Failed to fetch user:", error);
throw new Error("Failed to fetch user.");
}
}
export const {auth, signIn, signOut} = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials: any) {
const parsedCredentials = z.object({email: z.string().email(), password: z.string().min(6)}).safeParse(credentials);
if (parsedCredentials.success) {
const {email, password} = parsedCredentials.data;
const user = await getUser(email);
if (!user) return null;
// Check if the user has exceeded the max failed login attempts
if (user.failed_login_attempts >= MAX_FAILED_ATTEMPTS) {
await lockUserAccount(email);
throw new Error("Too many failed attempts. Your account has been locked.");
}
const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) {
await resetFailedAttempts(email);
return user;
} else {
await incrementFailedAttempts(email);
if (user.failed_login_attempts + 1 >= MAX_FAILED_ATTEMPTS) {
await lockUserAccount(email);
throw new Error("Too many failed attempts. Your account has been locked.");
}
console.log("Invalid credentials");
return null;
}
}
console.log("Invalid credentials");
return null;
},
}),
],
});