Enable DOMAIN_ADMIN to manage users within their authorized domains

This commit is contained in:
AyrisAI
2026-05-14 21:38:31 +03:00
parent b8648fb5f7
commit ede38e80e4
4 changed files with 108 additions and 28 deletions

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import { useDictionary } from "@/components/DictionaryContext"; import { useDictionary } from "@/components/DictionaryContext";
interface User { interface User {
@@ -19,6 +20,7 @@ export default function UsersPage() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null); const [editingUser, setEditingUser] = useState<User | null>(null);
const { data: session } = useSession();
// Form state // Form state
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@@ -242,22 +244,36 @@ export default function UsersPage() {
<input className="input" type="password" value={formData.password} onChange={e => setFormData({...formData, password: e.target.value})} required={!editingUser} /> <input className="input" type="password" value={formData.password} onChange={e => setFormData({...formData, password: e.target.value})} required={!editingUser} />
</div> </div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}> <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
<div> {session?.user?.role === "SUPER_ADMIN" ? (
<label className="label">Rol</label> <div>
<select className="input" value={formData.role} onChange={e => setFormData({...formData, role: e.target.value})}> <label className="label">Rol</label>
<option value="SUPER_ADMIN">Süper Admin</option> <select className="input" value={formData.role} onChange={e => setFormData({...formData, role: e.target.value})}>
<option value="DOMAIN_ADMIN">Domain Admin</option> <option value="SUPER_ADMIN">Süper Admin</option>
</select> <option value="DOMAIN_ADMIN">Domain Admin</option>
</div> </select>
</div>
) : (
<div>
<label className="label">Rol</label>
<input className="input" value="Domain Admin" disabled />
</div>
)}
<div> <div>
<label className="label">Telegram ID</label> <label className="label">Telegram ID</label>
<input className="input" placeholder="Örn: 5009005027" value={formData.telegramId} onChange={e => setFormData({...formData, telegramId: e.target.value})} /> <input className="input" placeholder="Örn: 5009005027" value={formData.telegramId} onChange={e => setFormData({...formData, telegramId: e.target.value})} />
</div> </div>
</div> </div>
<div> {session?.user?.role === "SUPER_ADMIN" ? (
<label className="label">İzinli Domainler (Virgülle ayırın, tümü için *)</label> <div>
<input className="input" placeholder="domain1.com, domain2.com" value={formData.domains} onChange={e => setFormData({...formData, domains: e.target.value})} /> <label className="label">İzinli Domainler (Virgülle ayırın, tümü için *)</label>
</div> <input className="input" placeholder="domain1.com, domain2.com" value={formData.domains} onChange={e => setFormData({...formData, domains: e.target.value})} />
</div>
) : (
<div>
<label className="label">İzinli Domainler</label>
<input className="input" value={session?.user?.domains?.join(", ")} disabled />
</div>
)}
<div style={{ display: "flex", gap: 10, marginTop: 10 }}> <div style={{ display: "flex", gap: 10, marginTop: 10 }}>
<button type="button" className="btn btn-ghost" style={{ flex: 1 }} onClick={() => setIsModalOpen(false)}>İptal</button> <button type="button" className="btn btn-ghost" style={{ flex: 1 }} onClick={() => setIsModalOpen(false)}>İptal</button>
<button type="submit" className="btn btn-primary" style={{ flex: 1 }} disabled={saving}> <button type="submit" className="btn btn-primary" style={{ flex: 1 }} disabled={saving}>

View File

@@ -7,22 +7,44 @@ export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id
const session = await auth(); const session = await auth();
const { id } = await params; const { id } = await params;
if (!session || session.user.role !== "SUPER_ADMIN") { if (!session) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const userRole = session.user.role;
const adminDomains = session.user.domains || [];
try { try {
// Mevcut kullanıcıyı kontrol et
const existingUser = await prisma.user.findUnique({ where: { id } });
if (!existingUser) return NextResponse.json({ error: "User not found" }, { status: 404 });
// Güvenlik Kontrolü: Domain admin sadece kendi domainindeki kullanıcıyı güncelleyebilir
if (userRole !== "SUPER_ADMIN") {
const hasAccess = existingUser.domains.some(d => adminDomains.includes(d));
if (!hasAccess) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await req.json(); const body = await req.json();
const { name, email, password, role, domains, telegramId } = body; const { name, email, password, role, domains, telegramId } = body;
let finalDomains = domains;
let finalRole = role;
// Güvenlik: Domain admin yetki yükseltemez veya domain değiştiremez
if (userRole !== "SUPER_ADMIN") {
finalDomains = adminDomains; // Kendi domainlerine kilitler
finalRole = "DOMAIN_ADMIN";
}
const user = await prisma.user.update({ const user = await prisma.user.update({
where: { id }, where: { id },
data: { data: {
name, name,
email: email?.toLowerCase(), email: email?.toLowerCase(),
password, password,
role, role: finalRole,
domains, domains: finalDomains,
telegramId, telegramId,
}, },
}); });
@@ -38,11 +60,23 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
const session = await auth(); const session = await auth();
const { id } = await params; const { id } = await params;
if (!session || session.user.role !== "SUPER_ADMIN") { if (!session) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const userRole = session.user.role;
const adminDomains = session.user.domains || [];
try { try {
const existingUser = await prisma.user.findUnique({ where: { id } });
if (!existingUser) return NextResponse.json({ error: "User not found" }, { status: 404 });
// Güvenlik Kontrolü
if (userRole !== "SUPER_ADMIN") {
const hasAccess = existingUser.domains.some(d => adminDomains.includes(d));
if (!hasAccess) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
await prisma.user.delete({ await prisma.user.delete({
where: { id }, where: { id },
}); });

View File

@@ -5,13 +5,30 @@ import { prisma } from "@/lib/prisma";
// GET /api/users — list all users // GET /api/users — list all users
export async function GET() { export async function GET() {
const session = await auth(); const session = await auth();
if (!session || session.user.role !== "SUPER_ADMIN") { if (!session) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const users = await prisma.user.findMany({ const userRole = session.user.role;
orderBy: { createdAt: "asc" }, const userDomains = session.user.domains || [];
});
let users;
if (userRole === "SUPER_ADMIN") {
// Super admin her şeyi görür
users = await prisma.user.findMany({
orderBy: { createdAt: "asc" },
});
} else {
// Domain admin sadece kendi domainlerine dokunan kullanıcıları görür
users = await prisma.user.findMany({
where: {
domains: {
hasSome: userDomains
}
},
orderBy: { createdAt: "asc" },
});
}
return NextResponse.json(users); return NextResponse.json(users);
} }
@@ -19,21 +36,34 @@ export async function GET() {
// POST /api/users — create a new user // POST /api/users — create a new user
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
const session = await auth(); const session = await auth();
if (!session || session.user.role !== "SUPER_ADMIN") { if (!session) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const userRole = session.user.role;
const adminDomains = session.user.domains || [];
try { try {
const body = await req.json(); const body = await req.json();
const { name, email, password, role, domains, telegramId } = body; const { name, email, password, role, domains, telegramId } = body;
let finalDomains = domains || [];
let finalRole = role || "DOMAIN_ADMIN";
// Güvenlik: Domain admin yetkisini aşamaz
if (userRole !== "SUPER_ADMIN") {
// Eğer domain admin ise, yeni kullanıcıya sadece kendi domainlerini verebilir
finalDomains = adminDomains;
finalRole = "DOMAIN_ADMIN"; // Başka bir super admin oluşturamaz
}
const user = await prisma.user.create({ const user = await prisma.user.create({
data: { data: {
name, name,
email: email.toLowerCase(), email: email.toLowerCase(),
password, password,
role: role || "DOMAIN_ADMIN", role: finalRole,
domains: domains || [], domains: finalDomains,
telegramId, telegramId,
}, },
}); });

View File

@@ -24,8 +24,8 @@ export default function Sidebar({ dict, lang }: { dict: any; lang: string }) {
section: dict.sidebar?.management || "YÖNETİM", section: dict.sidebar?.management || "YÖNETİM",
items: [ items: [
{ href: `/${lang}/dashboard/domains`, label: dict.domains?.title || "Domainler", icon: GlobeIcon, roles: ["SUPER_ADMIN"] }, { href: `/${lang}/dashboard/domains`, label: dict.domains?.title || "Domainler", icon: GlobeIcon, roles: ["SUPER_ADMIN"] },
{ href: `/${lang}/dashboard/users`, label: dict.sidebar?.users || "Kullanıcılar", icon: UsersIcon, roles: ["SUPER_ADMIN"] }, { href: `/${lang}/dashboard/users`, label: dict.sidebar?.users || "Kullanıcılar", icon: UsersIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
{ href: `/${lang}/dashboard/mappings`, label: dict.sidebar?.mappings || "Eşleştirmeler", icon: LinkIcon, roles: ["SUPER_ADMIN"] }, { href: `/${lang}/dashboard/mappings`, label: dict.sidebar?.mappings || "Eşleştirmeler", icon: LinkIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
{ href: `/${lang}/dashboard/mailboxes`, label: dict.sidebar?.mailboxes || "Mail Hesapları", icon: MailIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] }, { href: `/${lang}/dashboard/mailboxes`, label: dict.sidebar?.mailboxes || "Mail Hesapları", icon: MailIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
{ href: `/${lang}/dashboard/logs`, label: dict.sidebar?.logs || "Loglar", icon: ListIcon, roles: ["SUPER_ADMIN"] }, { href: `/${lang}/dashboard/logs`, label: dict.sidebar?.logs || "Loglar", icon: ListIcon, roles: ["SUPER_ADMIN"] },
], ],