diff --git a/database/migrations/run_all.sql b/database/migrations/run_all.sql index ea40526..e216a01 100644 --- a/database/migrations/run_all.sql +++ b/database/migrations/run_all.sql @@ -43,6 +43,9 @@ BEGIN; \echo '=== Running 011_calendar_events.sql (Calendar Events) ===' \i 011_calendar_events.sql +\echo '=== Running 012_invites.sql (Invites) ===' +\i 012_invites.sql + \echo '=== Running 012_sent_emails.sql (Sent Email Log) ===' \i 012_sent_emails.sql diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index e7ad058..5f24e89 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -9,6 +9,7 @@ import { resetFailedAttempts, isAccountLocked, createSession, + setSessionContext, } from "@/lib/auth" export async function POST(request: NextRequest) { @@ -106,6 +107,7 @@ export async function POST(request: NextRequest) { ) await createSession(dbUser.id, dbUser.role_name) + await setSessionContext(dbUser.id, ipAddress) const user = mapDbUserToSessionUser(dbUser) diff --git a/src/app/api/settings/company/route.ts b/src/app/api/settings/company/route.ts index 8098107..87aa37c 100644 --- a/src/app/api/settings/company/route.ts +++ b/src/app/api/settings/company/route.ts @@ -46,10 +46,11 @@ export async function PATCH(request: NextRequest) { const body = await request.json() - await query( + const result = await query( `UPDATE company_settings SET 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.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 }) } catch (error) { console.error("Company settings PATCH error:", error) diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index 4f0be95..fd4b2ca 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server" import { query } from "@/lib/db" -import { hashPassword, getSessionUser } from "@/lib/auth" +import { hashPassword, getSessionUser, encryptPassword, setSessionContext } from "@/lib/auth" import { avatarSvgUrl } from "@/lib/avatar" export async function GET(request: NextRequest) { @@ -68,12 +68,15 @@ export async function POST(request: NextRequest) { const lastName = nameParts.slice(1).join(" ") || firstName const username = email.split("@")[0] const passwordHash = await hashPassword(password) + const passwordEncrypted = await encryptPassword(password) + + await setSessionContext(sessionUser.id) const result = await query( - `INSERT INTO users (username, email, password_hash, first_name, last_name, is_active, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7) + `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, $8) 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 = ( diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 2c9542e..c1ebbef 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -231,7 +231,7 @@ export default function LoginPage() {
-
+
Black Cipher

diff --git a/src/lib/auth.ts b/src/lib/auth.ts index cc58756..8794412 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -17,6 +17,7 @@ export interface SessionUser { id: string; username: string; email: string; + phone: string | null; firstName: string; lastName: string; role: string; @@ -85,7 +86,7 @@ export async function getUserByUsername(username: string) { export async function getUserById(id: string) { 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, r.name AS role_name FROM users u @@ -185,6 +186,7 @@ export function mapDbUserToSessionUser( id: dbUser.id as string, username: dbUser.username as string, email: dbUser.email as string, + phone: (dbUser.phone as string) || null, firstName: dbUser.first_name as string, lastName: dbUser.last_name as string, role: roleMapping[roleName] || roleName.toLowerCase(), @@ -201,7 +203,6 @@ export async function createSession(userId: string, role: string) { secure: process.env.NODE_ENV === "production", sameSite: "strict", path: "/", - maxAge: 60 * 60 * 24, // 24 hours }); return token; @@ -218,6 +219,27 @@ export async function destroySession() { }); } +export async function encryptPassword(password: string): Promise { + const result = await query("SELECT encrypt_password($1) AS encrypted", [password]); + return result.rows[0]?.encrypted; +} + +export async function decryptPassword(encrypted: string): Promise { + 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 { try { const cookieStore = await cookies(); diff --git a/src/types/index.ts b/src/types/index.ts index 83030e9..d5677d9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -4,12 +4,13 @@ export type LeadStatus = | "pending" | "closed" | "ignored"; -export type UserRole = "super_admin" | "admin" | "sales"; +export type UserRole = "super_admin" | "admin" | "sales" | "dev"; export interface User { id: string; name: string; email: string; + phone?: string; role: UserRole; active: boolean; avatar: string;