I Fixed the notifications
This commit is contained in:
+80
-73
@@ -1,35 +1,35 @@
|
||||
import { SignJWT, jwtVerify } from "jose"
|
||||
import bcrypt from "bcryptjs"
|
||||
import { query } from "@/lib/db"
|
||||
import { cookies } from "next/headers"
|
||||
import { SignJWT, jwtVerify } from "jose";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { query } from "@/lib/db";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const JWT_SECRET = new TextEncoder().encode(
|
||||
process.env.JWT_SECRET || "fallback-dev-secret-do-not-use-in-production"
|
||||
)
|
||||
process.env.JWT_SECRET || "fallback-dev-secret-do-not-use-in-production",
|
||||
);
|
||||
|
||||
const SESSION_COOKIE = "session"
|
||||
const MAX_FAILED_ATTEMPTS = 5
|
||||
const LOCKOUT_DURATION_MINUTES = 15
|
||||
const SESSION_COOKIE = "session";
|
||||
const MAX_FAILED_ATTEMPTS = 5;
|
||||
const LOCKOUT_DURATION_MINUTES = 15;
|
||||
|
||||
export interface SessionUser {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
role: string
|
||||
avatar: string
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, 12)
|
||||
return bcrypt.hash(password, 12);
|
||||
}
|
||||
|
||||
export async function comparePassword(
|
||||
password: string,
|
||||
hash: string
|
||||
hash: string,
|
||||
): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash)
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
export async function signToken(payload: { userId: string; role: string }) {
|
||||
@@ -37,15 +37,15 @@ export async function signToken(payload: { userId: string; role: string }) {
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setExpirationTime("24h")
|
||||
.setIssuedAt()
|
||||
.sign(JWT_SECRET)
|
||||
.sign(JWT_SECRET);
|
||||
}
|
||||
|
||||
export async function verifyToken(token: string) {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, JWT_SECRET)
|
||||
return payload as { userId: string; role: string }
|
||||
const { payload } = await jwtVerify(token, JWT_SECRET);
|
||||
return payload as { userId: string; role: string };
|
||||
} catch {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ export async function getUserByEmail(email: string) {
|
||||
JOIN roles r ON r.id = ur.role_id
|
||||
WHERE u.email = $1 AND u.deleted_at IS NULL
|
||||
LIMIT 1`,
|
||||
[email.toLowerCase().trim()]
|
||||
)
|
||||
return result.rows[0] || null
|
||||
[email.toLowerCase().trim()],
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
export async function getUserByUsername(username: string) {
|
||||
@@ -76,9 +76,9 @@ export async function getUserByUsername(username: string) {
|
||||
JOIN roles r ON r.id = ur.role_id
|
||||
WHERE u.username = $1 AND u.deleted_at IS NULL
|
||||
LIMIT 1`,
|
||||
[username.toLowerCase().trim()]
|
||||
)
|
||||
return result.rows[0] || null
|
||||
[username.toLowerCase().trim()],
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
export async function getUserById(id: string) {
|
||||
@@ -91,9 +91,9 @@ export async function getUserById(id: string) {
|
||||
JOIN roles r ON r.id = ur.role_id
|
||||
WHERE u.id = $1 AND u.deleted_at IS NULL
|
||||
LIMIT 1`,
|
||||
[id]
|
||||
)
|
||||
return result.rows[0] || null
|
||||
[id],
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
export async function recordLoginAttempt(
|
||||
@@ -102,13 +102,20 @@ export async function recordLoginAttempt(
|
||||
ipAddress: string,
|
||||
userAgent: string | null,
|
||||
wasSuccessful: boolean,
|
||||
failureReason?: string
|
||||
failureReason?: string,
|
||||
) {
|
||||
await query(
|
||||
`INSERT INTO login_attempts (user_id, username_attempted, ip_address, user_agent, was_successful, failure_reason)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[userId, usernameAttempted, ipAddress, userAgent, wasSuccessful, failureReason || null]
|
||||
)
|
||||
[
|
||||
userId,
|
||||
usernameAttempted,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
wasSuccessful,
|
||||
failureReason || null,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function incrementFailedAttempts(userId: string) {
|
||||
@@ -121,8 +128,8 @@ export async function incrementFailedAttempts(userId: string) {
|
||||
ELSE locked_until
|
||||
END
|
||||
WHERE id = $1`,
|
||||
[userId, MAX_FAILED_ATTEMPTS, LOCKOUT_DURATION_MINUTES]
|
||||
)
|
||||
[userId, MAX_FAILED_ATTEMPTS, LOCKOUT_DURATION_MINUTES],
|
||||
);
|
||||
}
|
||||
|
||||
export async function resetFailedAttempts(userId: string) {
|
||||
@@ -132,38 +139,38 @@ export async function resetFailedAttempts(userId: string) {
|
||||
locked_until = NULL,
|
||||
last_login_at = NOW()
|
||||
WHERE id = $1`,
|
||||
[userId]
|
||||
)
|
||||
[userId],
|
||||
);
|
||||
}
|
||||
|
||||
export async function isAccountLocked(user: {
|
||||
is_locked: boolean
|
||||
locked_until: Date | null
|
||||
is_locked: boolean;
|
||||
locked_until: Date | null;
|
||||
}): Promise<{ locked: boolean; reason?: string }> {
|
||||
if (user.is_locked) {
|
||||
return { locked: true, reason: "Account has been locked by an administrator." }
|
||||
return {
|
||||
locked: true,
|
||||
reason: "Account has been locked by an administrator.",
|
||||
};
|
||||
}
|
||||
if (user.locked_until && new Date(user.locked_until) > new Date()) {
|
||||
const minutes = Math.ceil(
|
||||
(new Date(user.locked_until).getTime() - Date.now()) / 60000
|
||||
)
|
||||
(new Date(user.locked_until).getTime() - Date.now()) / 60000,
|
||||
);
|
||||
return {
|
||||
locked: true,
|
||||
reason: `Account is temporarily locked due to too many failed attempts. Try again in ${minutes} minute(s).`,
|
||||
}
|
||||
};
|
||||
}
|
||||
return { locked: false }
|
||||
return { locked: false };
|
||||
}
|
||||
|
||||
export function mapDbUserToSessionUser(dbUser: Record<string, unknown>): SessionUser {
|
||||
const roleName = dbUser.role_name as string
|
||||
const roleMap: Record<string, string> = {
|
||||
SUPER_ADMIN: "super_admin",
|
||||
ADMIN: "admin",
|
||||
DEVELOPER: "developer",
|
||||
SALES_USER: "sales",
|
||||
}
|
||||
const mappedRole = roleMap[roleName] || "sales"
|
||||
export function mapDbUserToSessionUser(
|
||||
dbUser: Record<string, unknown>,
|
||||
): SessionUser {
|
||||
const roleName = dbUser.role_name as string;
|
||||
const mappedRole =
|
||||
roleName === "SUPER_ADMIN" || roleName === "ADMIN" ? "admin" : "sales";
|
||||
|
||||
return {
|
||||
id: dbUser.id as string,
|
||||
@@ -171,53 +178,53 @@ export function mapDbUserToSessionUser(dbUser: Record<string, unknown>): Session
|
||||
email: dbUser.email as string,
|
||||
firstName: dbUser.first_name as string,
|
||||
lastName: dbUser.last_name as string,
|
||||
role: mappedRole,
|
||||
role: roleName,
|
||||
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(
|
||||
`${dbUser.first_name}+${dbUser.last_name}`
|
||||
`${dbUser.first_name}+${dbUser.last_name}`,
|
||||
)}&background=1d4ed8&color=fff&size=128`,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function createSession(userId: string, role: string) {
|
||||
const token = await signToken({ userId, role })
|
||||
const token = await signToken({ userId, role });
|
||||
|
||||
const cookieStore = await cookies()
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(SESSION_COOKIE, token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24, // 24 hours
|
||||
})
|
||||
});
|
||||
|
||||
return token
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function destroySession() {
|
||||
const cookieStore = await cookies()
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(SESSION_COOKIE, "", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 0,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSessionUser(): Promise<SessionUser | null> {
|
||||
try {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get(SESSION_COOKIE)?.value
|
||||
if (!token) return null
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get(SESSION_COOKIE)?.value;
|
||||
if (!token) return null;
|
||||
|
||||
const payload = await verifyToken(token)
|
||||
if (!payload) return null
|
||||
const payload = await verifyToken(token);
|
||||
if (!payload) return null;
|
||||
|
||||
const dbUser = await getUserById(payload.userId)
|
||||
if (!dbUser) return null
|
||||
const dbUser = await getUserById(payload.userId);
|
||||
if (!dbUser) return null;
|
||||
|
||||
return mapDbUserToSessionUser(dbUser)
|
||||
return mapDbUserToSessionUser(dbUser);
|
||||
} catch {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user