ATTEMPTED to fix my databse stuff, security etc, and a Issue Cait had

This commit is contained in:
2026-06-26 18:37:28 +02:00
parent fe09d15359
commit bccba1434b
7 changed files with 57 additions and 10 deletions
+3
View File
@@ -43,6 +43,9 @@ BEGIN;
\echo '=== Running 011_calendar_events.sql (Calendar Events) ===' \echo '=== Running 011_calendar_events.sql (Calendar Events) ==='
\i 011_calendar_events.sql \i 011_calendar_events.sql
\echo '=== Running 012_invites.sql (Invites) ==='
\i 012_invites.sql
\echo '=== Running 012_sent_emails.sql (Sent Email Log) ===' \echo '=== Running 012_sent_emails.sql (Sent Email Log) ==='
\i 012_sent_emails.sql \i 012_sent_emails.sql
+2
View File
@@ -9,6 +9,7 @@ import {
resetFailedAttempts, resetFailedAttempts,
isAccountLocked, isAccountLocked,
createSession, createSession,
setSessionContext,
} from "@/lib/auth" } from "@/lib/auth"
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
@@ -106,6 +107,7 @@ export async function POST(request: NextRequest) {
) )
await createSession(dbUser.id, dbUser.role_name) await createSession(dbUser.id, dbUser.role_name)
await setSessionContext(dbUser.id, ipAddress)
const user = mapDbUserToSessionUser(dbUser) const user = mapDbUserToSessionUser(dbUser)
+18 -2
View File
@@ -46,10 +46,11 @@ export async function PATCH(request: NextRequest) {
const body = await request.json() const body = await request.json()
await query( const result = await query(
`UPDATE company_settings SET `UPDATE company_settings SET
company_name = $1, company_email = $2, company_phone = $3, company_name = $1, company_email = $2, company_phone = $3,
company_website = $4, company_address = $5, updated_by = $6, updated_at = NOW()`, company_website = $4, company_address = $5, updated_by = $6, updated_at = NOW()
WHERE id = (SELECT id FROM company_settings ORDER BY updated_at DESC LIMIT 1)`,
[ [
body.companyName || "", body.companyName || "",
body.companyEmail || "", body.companyEmail || "",
@@ -60,6 +61,21 @@ export async function PATCH(request: NextRequest) {
], ],
) )
if (result.rowCount === 0) {
await query(
`INSERT INTO company_settings (company_name, company_email, company_phone, company_website, company_address, updated_by)
VALUES ($1, $2, $3, $4, $5, $6)`,
[
body.companyName || "",
body.companyEmail || "",
body.companyPhone || "",
body.companyWebsite || "",
body.companyAddress || "",
user.id,
],
)
}
return NextResponse.json({ success: true }) return NextResponse.json({ success: true })
} catch (error) { } catch (error) {
console.error("Company settings PATCH error:", error) console.error("Company settings PATCH error:", error)
+7 -4
View File
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server"
import { query } from "@/lib/db" import { query } from "@/lib/db"
import { hashPassword, getSessionUser } from "@/lib/auth" import { hashPassword, getSessionUser, encryptPassword, setSessionContext } from "@/lib/auth"
import { avatarSvgUrl } from "@/lib/avatar" import { avatarSvgUrl } from "@/lib/avatar"
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
@@ -68,12 +68,15 @@ export async function POST(request: NextRequest) {
const lastName = nameParts.slice(1).join(" ") || firstName const lastName = nameParts.slice(1).join(" ") || firstName
const username = email.split("@")[0] const username = email.split("@")[0]
const passwordHash = await hashPassword(password) const passwordHash = await hashPassword(password)
const passwordEncrypted = await encryptPassword(password)
await setSessionContext(sessionUser.id)
const result = await query( const result = await query(
`INSERT INTO users (username, email, password_hash, first_name, last_name, is_active, created_by) `INSERT INTO users (username, email, password_hash, password_encrypted, first_name, last_name, is_active, created_by)
VALUES ($1, $2, $3, $4, $5, $6, $7) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id`, RETURNING id`,
[username.toLowerCase(), email.toLowerCase(), passwordHash, firstName, lastName, active ?? true, sessionUser.id] [username.toLowerCase(), email.toLowerCase(), passwordHash, passwordEncrypted, firstName, lastName, active ?? true, sessionUser.id]
) )
const roleId = ( const roleId = (
+1 -1
View File
@@ -231,7 +231,7 @@ export default function LoginPage() {
<div className="left-panel flex flex-1 flex-col p-12"> <div className="left-panel flex flex-1 flex-col p-12">
<div className="relative z-10 flex-1 flex items-center justify-center"> <div className="relative z-10 flex-1 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="bc-logo mb-8" style={{"--theme-primary":"#1BB0CE"}}> <div className="bc-logo mb-8" style={{"--theme-primary":"#1BB0CE"} as React.CSSProperties}>
Black <span className="accent">Cipher</span> Black <span className="accent">Cipher</span>
</div> </div>
<h1 className="text-[34px] font-extrabold text-[#e8e8ef] leading-tight tracking-[-0.6px]"> <h1 className="text-[34px] font-extrabold text-[#e8e8ef] leading-tight tracking-[-0.6px]">
+24 -2
View File
@@ -17,6 +17,7 @@ export interface SessionUser {
id: string; id: string;
username: string; username: string;
email: string; email: string;
phone: string | null;
firstName: string; firstName: string;
lastName: string; lastName: string;
role: string; role: string;
@@ -85,7 +86,7 @@ export async function getUserByUsername(username: string) {
export async function getUserById(id: string) { export async function getUserById(id: string) {
const result = await query( const result = await query(
` SELECT u.id, u.username, u.email, u.first_name, u.last_name, ` SELECT u.id, u.username, u.email, u.phone, u.first_name, u.last_name,
u.is_active, u.avatar_url, u.is_active, u.avatar_url,
r.name AS role_name r.name AS role_name
FROM users u FROM users u
@@ -185,6 +186,7 @@ export function mapDbUserToSessionUser(
id: dbUser.id as string, id: dbUser.id as string,
username: dbUser.username as string, username: dbUser.username as string,
email: dbUser.email as string, email: dbUser.email as string,
phone: (dbUser.phone as string) || null,
firstName: dbUser.first_name as string, firstName: dbUser.first_name as string,
lastName: dbUser.last_name as string, lastName: dbUser.last_name as string,
role: roleMapping[roleName] || roleName.toLowerCase(), role: roleMapping[roleName] || roleName.toLowerCase(),
@@ -201,7 +203,6 @@ export async function createSession(userId: string, role: string) {
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
sameSite: "strict", sameSite: "strict",
path: "/", path: "/",
maxAge: 60 * 60 * 24, // 24 hours
}); });
return token; return token;
@@ -218,6 +219,27 @@ export async function destroySession() {
}); });
} }
export async function encryptPassword(password: string): Promise<string> {
const result = await query("SELECT encrypt_password($1) AS encrypted", [password]);
return result.rows[0]?.encrypted;
}
export async function decryptPassword(encrypted: string): Promise<string | null> {
try {
const result = await query("SELECT decrypt_password($1) AS decrypted", [encrypted]);
return result.rows[0]?.decrypted || null;
} catch {
return null;
}
}
export async function setSessionContext(userId: string, ip?: string) {
await query("SELECT set_config('app.current_user_id', $1, true)", [userId]);
if (ip) {
await query("SELECT set_config('app.current_ip', $1, true)", [ip]);
}
}
export async function getSessionUser(): Promise<SessionUser | null> { export async function getSessionUser(): Promise<SessionUser | null> {
try { try {
const cookieStore = await cookies(); const cookieStore = await cookies();
+2 -1
View File
@@ -4,12 +4,13 @@ export type LeadStatus =
| "pending" | "pending"
| "closed" | "closed"
| "ignored"; | "ignored";
export type UserRole = "super_admin" | "admin" | "sales"; export type UserRole = "super_admin" | "admin" | "sales" | "dev";
export interface User { export interface User {
id: string; id: string;
name: string; name: string;
email: string; email: string;
phone?: string;
role: UserRole; role: UserRole;
active: boolean; active: boolean;
avatar: string; avatar: string;