- {hasPermission('servers:start') && server.status === 'offline' && (
+ {canDo('servers:start') && server.status === 'offline' && (
)}
- {hasPermission('servers:stop') && server.status === 'online' && (
+ {canDo('servers:stop') && server.status === 'online' && (
)}
- {hasPermission('servers:restart') && server.status === 'online' && (
+ {canDo('servers:restart') && server.status === 'online' && (
@@ -259,7 +264,7 @@ export default function ServerDetailPage() {
{/* Navigation Cards */}
{navLinks
- .filter(link => hasPermission(link.permission))
+ .filter(link => canDo(link.permission))
.map(link => {
const Icon = link.icon
return (
diff --git a/src/app/api/servers/[id]/admins/[userId]/route.ts b/src/app/api/servers/[id]/admins/[userId]/route.ts
new file mode 100644
index 0000000..550366b
--- /dev/null
+++ b/src/app/api/servers/[id]/admins/[userId]/route.ts
@@ -0,0 +1,68 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { validateSession, hasPermission } from '@/lib/auth'
+import connectToDatabase from '@/lib/mongodb'
+import { Server, User } from '@/lib/models'
+import { isValidObjectId } from '@/lib/input-validation'
+import { createAuditLog, getClientIP } from '@/lib/audit'
+
+// DELETE /api/servers/[id]/admins/[userId] — Remove a server admin
+export async function DELETE(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string; userId: string }> }
+) {
+ const clientIP = getClientIP(request)
+
+ try {
+ const session = await validateSession(request)
+ if (!session) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ if (!hasPermission(session, 'servers:edit')) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
+ const { id, userId } = await params
+ if (!isValidObjectId(id) || !isValidObjectId(userId)) {
+ return NextResponse.json({ error: 'Invalid ID' }, { status: 400 })
+ }
+
+ await connectToDatabase()
+ const server = await Server.findById(id)
+ if (!server) {
+ return NextResponse.json({ error: 'Server not found' }, { status: 404 })
+ }
+
+ const isAdmin = server.admins.some((a) => a.toString() === userId)
+ if (!isAdmin) {
+ return NextResponse.json({ error: 'User is not an admin of this server' }, { status: 404 })
+ }
+
+ if (server.admins.length <= 1) {
+ return NextResponse.json({ error: 'Cannot remove the last admin from a server' }, { status: 400 })
+ }
+
+ server.admins = server.admins.filter((a) => a.toString() !== userId)
+ await server.save()
+
+ const removedUser = await User.findById(userId, { username: 1 }).lean()
+
+ await createAuditLog({
+ action: 'server_admin_removed',
+ entityType: 'server',
+ entityName: server.name,
+ userId: session._id,
+ userName: session.username,
+ userEmail: session.email,
+ previousValues: { serverId: server._id.toString(), removedUserId: userId, removedUserName: removedUser?.username },
+ clientIP,
+ status: 'success',
+ statusCode: 200,
+ })
+
+ return NextResponse.json({ success: true })
+ } catch (error) {
+ console.error('Remove admin error:', error)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
diff --git a/src/app/api/servers/[id]/admins/route.ts b/src/app/api/servers/[id]/admins/route.ts
new file mode 100644
index 0000000..0ad5504
--- /dev/null
+++ b/src/app/api/servers/[id]/admins/route.ts
@@ -0,0 +1,115 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { validateSession, hasPermission, hasServerPermission } from '@/lib/auth'
+import connectToDatabase from '@/lib/mongodb'
+import { Server, User } from '@/lib/models'
+import { isValidObjectId } from '@/lib/input-validation'
+import { createAuditLog, getClientIP } from '@/lib/audit'
+import mongoose from 'mongoose'
+
+// GET /api/servers/[id]/admins — List server admins
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const session = await validateSession(request)
+ if (!session) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ const { id } = await params
+ if (!isValidObjectId(id)) {
+ return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
+ }
+
+ await connectToDatabase()
+ const server = await Server.findById(id)
+ if (!server) {
+ return NextResponse.json({ error: 'Server not found' }, { status: 404 })
+ }
+
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:view', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
+ // Populate admin users
+ const adminUsers = await User.find(
+ { _id: { $in: server.admins } },
+ { _id: 1, username: 1, email: 1 }
+ ).lean()
+
+ return NextResponse.json({ success: true, data: adminUsers })
+ } catch (error) {
+ console.error('Fetch admins error:', error)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
+
+// POST /api/servers/[id]/admins — Add a user as server admin
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const clientIP = getClientIP(request)
+
+ try {
+ const session = await validateSession(request)
+ if (!session) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ if (!hasPermission(session, 'servers:edit')) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
+ const { id } = await params
+ if (!isValidObjectId(id)) {
+ return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
+ }
+
+ const body = await request.json()
+ const { userId } = body
+
+ if (!userId || !isValidObjectId(userId)) {
+ return NextResponse.json({ error: 'Invalid user ID' }, { status: 400 })
+ }
+
+ await connectToDatabase()
+ const server = await Server.findById(id)
+ if (!server) {
+ return NextResponse.json({ error: 'Server not found' }, { status: 404 })
+ }
+
+ const targetUser = await User.findById(userId, { _id: 1, username: 1, email: 1 }).lean()
+ if (!targetUser) {
+ return NextResponse.json({ error: 'User not found' }, { status: 404 })
+ }
+
+ const alreadyAdmin = server.admins.some((a) => a.toString() === userId)
+ if (alreadyAdmin) {
+ return NextResponse.json({ error: 'User is already an admin of this server' }, { status: 409 })
+ }
+
+ server.admins.push(new mongoose.Types.ObjectId(userId))
+ await server.save()
+
+ await createAuditLog({
+ action: 'server_admin_added',
+ entityType: 'server',
+ entityName: server.name,
+ userId: session._id,
+ userName: session.username,
+ userEmail: session.email,
+ newValues: { serverId: server._id.toString(), addedUserId: userId, addedUserName: targetUser.username },
+ clientIP,
+ status: 'success',
+ statusCode: 201,
+ })
+
+ return NextResponse.json({ success: true, data: targetUser }, { status: 201 })
+ } catch (error) {
+ console.error('Add admin error:', error)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
diff --git a/src/app/api/servers/[id]/backups/[backupId]/restore/route.ts b/src/app/api/servers/[id]/backups/[backupId]/restore/route.ts
index 1cebbdc..5e7b3fd 100644
--- a/src/app/api/servers/[id]/backups/[backupId]/restore/route.ts
+++ b/src/app/api/servers/[id]/backups/[backupId]/restore/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Backup, Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -21,10 +21,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'backups:restore')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id, backupId } = await params
if (!isValidObjectId(id) || !isValidObjectId(backupId)) {
return NextResponse.json({ error: 'Invalid ID' }, { status: 400 })
@@ -36,6 +32,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'backups:restore', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const backup = await Backup.findOne({ _id: backupId, serverId: id })
if (!backup || backup.status !== 'completed') {
return NextResponse.json({ error: 'Backup not found or incomplete' }, { status: 404 })
diff --git a/src/app/api/servers/[id]/backups/[backupId]/route.ts b/src/app/api/servers/[id]/backups/[backupId]/route.ts
index 072453b..34f04d5 100644
--- a/src/app/api/servers/[id]/backups/[backupId]/route.ts
+++ b/src/app/api/servers/[id]/backups/[backupId]/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
-import { Backup } from '@/lib/models'
+import { Backup, Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
import { createAuditLog, getClientIP } from '@/lib/audit'
import { unlink } from 'fs/promises'
@@ -19,16 +19,22 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'backups:delete')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id, backupId } = await params
if (!isValidObjectId(id) || !isValidObjectId(backupId)) {
return NextResponse.json({ error: 'Invalid ID' }, { status: 400 })
}
await connectToDatabase()
+ const server = await Server.findById(id).lean()
+ if (!server) {
+ return NextResponse.json({ error: 'Server not found' }, { status: 404 })
+ }
+
+ const adminIds = (server.admins ?? []).map((a) => a.toString())
+ if (!hasServerPermission(session, 'backups:delete', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const backup = await Backup.findOne({ _id: backupId, serverId: id })
if (!backup) {
return NextResponse.json({ error: 'Backup not found' }, { status: 404 })
diff --git a/src/app/api/servers/[id]/backups/route.ts b/src/app/api/servers/[id]/backups/route.ts
index 45019ab..d32b4be 100644
--- a/src/app/api/servers/[id]/backups/route.ts
+++ b/src/app/api/servers/[id]/backups/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server, Backup } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -20,16 +20,22 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'backups:view')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
}
await connectToDatabase()
+ const server = await Server.findById(id).lean()
+ if (!server) {
+ return NextResponse.json({ error: 'Server not found' }, { status: 404 })
+ }
+
+ const adminIds = (server.admins ?? []).map((a) => a.toString())
+ if (!hasServerPermission(session, 'backups:view', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const backups = await Backup.find({ serverId: id }).sort({ createdAt: -1 }).lean()
return NextResponse.json({ success: true, data: backups })
@@ -52,10 +58,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'backups:create')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -67,6 +69,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'backups:create', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const serverDir = getServerPath(server._id.toString())
const backupsDir = path.join(serverDir, 'backups')
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
diff --git a/src/app/api/servers/[id]/configuration/route.ts b/src/app/api/servers/[id]/configuration/route.ts
index a52806c..e35e09d 100644
--- a/src/app/api/servers/[id]/configuration/route.ts
+++ b/src/app/api/servers/[id]/configuration/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { sanitizeObject, isValidObjectId } from '@/lib/input-validation'
@@ -19,10 +19,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -34,6 +30,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const serverDir = getServerPath(server._id.toString())
const propsPath = path.join(serverDir, 'server.properties')
@@ -80,10 +81,6 @@ export async function PUT(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -98,6 +95,11 @@ export async function PUT(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const serverDir = getServerPath(server._id.toString())
const propsPath = path.join(serverDir, 'server.properties')
diff --git a/src/app/api/servers/[id]/console/route.ts b/src/app/api/servers/[id]/console/route.ts
index 5a9e610..9a2f0c6 100644
--- a/src/app/api/servers/[id]/console/route.ts
+++ b/src/app/api/servers/[id]/console/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -16,10 +16,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:console')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -31,6 +27,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:console', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const container = await getContainerByName(`mc-${server._id}`)
if (!container) {
return NextResponse.json({ error: 'Container not found' }, { status: 404 })
@@ -93,10 +94,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:console')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -113,6 +110,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:console', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.status !== 'online') {
return NextResponse.json({ error: 'Server is not running' }, { status: 400 })
}
diff --git a/src/app/api/servers/[id]/files/route.ts b/src/app/api/servers/[id]/files/route.ts
index 126c85d..5b81ca0 100644
--- a/src/app/api/servers/[id]/files/route.ts
+++ b/src/app/api/servers/[id]/files/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -48,10 +48,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -63,6 +59,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const requestedPath = request.nextUrl.searchParams.get('path') || '/'
const readContent = request.nextUrl.searchParams.get('read') === 'true'
@@ -176,10 +177,6 @@ export async function PUT(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -191,6 +188,11 @@ export async function PUT(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const body = await request.json()
const { path: filePath, content } = body
@@ -228,10 +230,6 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -243,6 +241,11 @@ export async function DELETE(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const { path: filePath } = await request.json()
if (!filePath) {
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
@@ -283,10 +286,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -298,6 +297,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const serverRoot = resolve(getServerPath(server._id.toString()))
const contentType = request.headers.get('content-type') || ''
diff --git a/src/app/api/servers/[id]/logs/route.ts b/src/app/api/servers/[id]/logs/route.ts
index 7057ef1..df97abb 100644
--- a/src/app/api/servers/[id]/logs/route.ts
+++ b/src/app/api/servers/[id]/logs/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -18,10 +18,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:console')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -33,6 +29,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:console', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const logsDir = join(getServerPath(server._id.toString()), 'logs')
const fileName = request.nextUrl.searchParams.get('file')
diff --git a/src/app/api/servers/[id]/mods/[filename]/route.ts b/src/app/api/servers/[id]/mods/[filename]/route.ts
index eac3995..80cb0ad 100644
--- a/src/app/api/servers/[id]/mods/[filename]/route.ts
+++ b/src/app/api/servers/[id]/mods/[filename]/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -21,10 +21,6 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'mods:remove')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id, filename } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -38,6 +34,11 @@ export async function DELETE(
return NextResponse.json({ error: 'Server not found or not a Forge/Fabric server' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'mods:remove', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const filePath = path.join(getServerPath(server._id.toString()), 'mods', decodedFilename)
try {
diff --git a/src/app/api/servers/[id]/mods/[filename]/toggle/route.ts b/src/app/api/servers/[id]/mods/[filename]/toggle/route.ts
index d43ffe3..3d695ef 100644
--- a/src/app/api/servers/[id]/mods/[filename]/toggle/route.ts
+++ b/src/app/api/servers/[id]/mods/[filename]/toggle/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -21,10 +21,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'mods:toggle')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id, filename } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -38,6 +34,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found or not a Forge/Fabric server' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'mods:toggle', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const modsDir = path.join(getServerPath(server._id.toString()), 'mods')
const currentPath = path.join(modsDir, decodedFilename)
diff --git a/src/app/api/servers/[id]/mods/route.ts b/src/app/api/servers/[id]/mods/route.ts
index ea705a6..9e12239 100644
--- a/src/app/api/servers/[id]/mods/route.ts
+++ b/src/app/api/servers/[id]/mods/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -19,10 +19,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'mods:view')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -34,6 +30,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'mods:view', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.type !== 'forge' && server.type !== 'fabric') {
return NextResponse.json({ error: 'Mods are only available for Forge/Fabric servers' }, { status: 400 })
}
@@ -81,10 +82,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'mods:install')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -96,6 +93,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'mods:install', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.type !== 'forge' && server.type !== 'fabric') {
return NextResponse.json({ error: 'Mods are only available for Forge/Fabric servers' }, { status: 400 })
}
diff --git a/src/app/api/servers/[id]/players/[name]/[action]/route.ts b/src/app/api/servers/[id]/players/[name]/[action]/route.ts
index b8a5fce..e4dc00b 100644
--- a/src/app/api/servers/[id]/players/[name]/[action]/route.ts
+++ b/src/app/api/servers/[id]/players/[name]/[action]/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -38,10 +38,6 @@ export async function POST(
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
}
- if (!hasPermission(session, requiredPermission)) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
}
@@ -52,6 +48,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, requiredPermission, adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
// Map action to MC command
const commandMap: Record = {
whitelist: `whitelist add ${decodedName}`,
diff --git a/src/app/api/servers/[id]/players/route.ts b/src/app/api/servers/[id]/players/route.ts
index 753452e..b973314 100644
--- a/src/app/api/servers/[id]/players/route.ts
+++ b/src/app/api/servers/[id]/players/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -27,10 +27,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'players:view')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -42,6 +38,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'players:view', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const serverDir = getServerPath(server._id.toString())
// Parse player lists from JSON files
diff --git a/src/app/api/servers/[id]/plugins/[filename]/route.ts b/src/app/api/servers/[id]/plugins/[filename]/route.ts
index b86745f..5382c45 100644
--- a/src/app/api/servers/[id]/plugins/[filename]/route.ts
+++ b/src/app/api/servers/[id]/plugins/[filename]/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -21,10 +21,6 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'plugins:remove')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id, filename } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -38,6 +34,11 @@ export async function DELETE(
return NextResponse.json({ error: 'Server not found or not a Bukkit server' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'plugins:remove', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const filePath = path.join(getServerPath(server._id.toString()), 'plugins', decodedFilename)
try {
diff --git a/src/app/api/servers/[id]/plugins/[filename]/toggle/route.ts b/src/app/api/servers/[id]/plugins/[filename]/toggle/route.ts
index 6c53fce..1d66073 100644
--- a/src/app/api/servers/[id]/plugins/[filename]/toggle/route.ts
+++ b/src/app/api/servers/[id]/plugins/[filename]/toggle/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -21,10 +21,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'plugins:toggle')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id, filename } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -38,6 +34,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found or not a Bukkit server' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'plugins:toggle', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const pluginsDir = path.join(getServerPath(server._id.toString()), 'plugins')
const currentPath = path.join(pluginsDir, decodedFilename)
diff --git a/src/app/api/servers/[id]/plugins/route.ts b/src/app/api/servers/[id]/plugins/route.ts
index 425a059..8601636 100644
--- a/src/app/api/servers/[id]/plugins/route.ts
+++ b/src/app/api/servers/[id]/plugins/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -19,10 +19,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'plugins:view')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -34,6 +30,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'plugins:view', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.type !== 'bukkit') {
return NextResponse.json({ error: 'Plugins are only available for Bukkit-type servers' }, { status: 400 })
}
@@ -81,10 +82,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'plugins:install')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -96,6 +93,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'plugins:install', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.type !== 'bukkit') {
return NextResponse.json({ error: 'Plugins are only available for Bukkit-type servers' }, { status: 400 })
}
diff --git a/src/app/api/servers/[id]/restart/route.ts b/src/app/api/servers/[id]/restart/route.ts
index d3456df..d9bdd1f 100644
--- a/src/app/api/servers/[id]/restart/route.ts
+++ b/src/app/api/servers/[id]/restart/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -18,10 +18,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:restart')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -34,6 +30,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:restart', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
// If the server is currently offline, check for port conflicts before restarting
if (server.status !== 'online' && server.status !== 'starting') {
const portConflict = await Server.findOne({
diff --git a/src/app/api/servers/[id]/route.ts b/src/app/api/servers/[id]/route.ts
index b7b7c67..0fa8d14 100644
--- a/src/app/api/servers/[id]/route.ts
+++ b/src/app/api/servers/[id]/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasPermission, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { sanitizeObject, isValidObjectId } from '@/lib/input-validation'
@@ -17,10 +17,6 @@ export async function GET(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:view')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -33,6 +29,11 @@ export async function GET(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = (server.admins ?? []).map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:view', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
// Reconcile status with Docker
try {
const container = await getContainerByName(`mc-${server._id}`)
@@ -69,10 +70,6 @@ export async function PUT(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:edit')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -88,6 +85,11 @@ export async function PUT(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:edit', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
const previousValues = server.toObject()
// Allowlisted update fields
diff --git a/src/app/api/servers/[id]/start/route.ts b/src/app/api/servers/[id]/start/route.ts
index 49fdc29..90d3f33 100644
--- a/src/app/api/servers/[id]/start/route.ts
+++ b/src/app/api/servers/[id]/start/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -18,10 +18,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:start')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -34,6 +30,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:start', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.status === 'online' || server.status === 'starting') {
return NextResponse.json({ error: 'Server is already running or starting' }, { status: 400 })
}
diff --git a/src/app/api/servers/[id]/stats/route.ts b/src/app/api/servers/[id]/stats/route.ts
index 70ee430..c1596aa 100644
--- a/src/app/api/servers/[id]/stats/route.ts
+++ b/src/app/api/servers/[id]/stats/route.ts
@@ -1,5 +1,5 @@
import { NextRequest } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -46,10 +46,6 @@ export async function GET(
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })
}
- if (!hasPermission(session, 'servers:view')) {
- return new Response(JSON.stringify({ error: 'Forbidden' }), { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return new Response(JSON.stringify({ error: 'Invalid server ID' }), { status: 400 })
@@ -61,6 +57,11 @@ export async function GET(
return new Response(JSON.stringify({ error: 'Server not found' }), { status: 404 })
}
+ const adminIds = (server.admins ?? []).map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:view', adminIds)) {
+ return new Response(JSON.stringify({ error: 'Forbidden' }), { status: 403 })
+ }
+
const container = await getContainerByName(`mc-${server._id}`).catch(() => null)
if (!container) {
return new Response(JSON.stringify({ error: 'Container not found' }), { status: 404 })
diff --git a/src/app/api/servers/[id]/stop/route.ts b/src/app/api/servers/[id]/stop/route.ts
index f5e8bf2..2819c8e 100644
--- a/src/app/api/servers/[id]/stop/route.ts
+++ b/src/app/api/servers/[id]/stop/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
-import { validateSession, hasPermission } from '@/lib/auth'
+import { validateSession, hasServerPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
import { isValidObjectId } from '@/lib/input-validation'
@@ -18,10 +18,6 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:stop')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
const { id } = await params
if (!isValidObjectId(id)) {
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 })
@@ -34,6 +30,11 @@ export async function POST(
return NextResponse.json({ error: 'Server not found' }, { status: 404 })
}
+ const adminIds = server.admins.map((a) => a.toString())
+ if (!hasServerPermission(session, 'servers:stop', adminIds)) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
if (server.status === 'offline' || server.status === 'stopping') {
return NextResponse.json({ error: 'Server is already stopped or stopping' }, { status: 400 })
}
diff --git a/src/app/api/servers/route.ts b/src/app/api/servers/route.ts
index 7a6b22b..fb939c8 100644
--- a/src/app/api/servers/route.ts
+++ b/src/app/api/servers/route.ts
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
+import mongoose from 'mongoose'
import { validateSession, hasPermission } from '@/lib/auth'
import connectToDatabase from '@/lib/mongodb'
import { Server } from '@/lib/models'
@@ -14,12 +15,17 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- if (!hasPermission(session, 'servers:view')) {
- return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
- }
-
await connectToDatabase()
- const servers = await Server.find().sort({ createdAt: -1 }).lean()
+
+ let servers
+ if (hasPermission(session, 'servers:view')) {
+ servers = await Server.find().sort({ createdAt: -1 }).lean()
+ } else {
+ // Return only servers where this user is a server admin
+ servers = await Server.find({
+ admins: new mongoose.Types.ObjectId(session._id),
+ }).sort({ createdAt: -1 }).lean()
+ }
return NextResponse.json({ success: true, data: servers })
} catch (error) {
@@ -95,6 +101,7 @@ export async function POST(request: NextRequest) {
autoStart: false,
autoRestart: true,
createdBy: session._id,
+ admins: [new mongoose.Types.ObjectId(session._id)],
})
// Set containerName before save — schema requires it
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index c000520..921b3e1 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -149,3 +149,18 @@ export function hasPermission(user: AuthUser, permission: string): boolean {
if (user.permissions.includes('*:*')) return true
return user.permissions.includes(permission)
}
+
+/**
+ * Checks permission for a specific server, granting access to server admins.
+ * Server admins have full access EXCEPT servers:delete (global-only).
+ */
+export function hasServerPermission(
+ user: AuthUser,
+ permission: string,
+ serverAdmins: string[]
+): boolean {
+ if (user.permissions.includes('*:*')) return true
+ if (user.permissions.includes(permission)) return true
+ if (permission !== 'servers:delete' && serverAdmins.includes(user._id)) return true
+ return false
+}
diff --git a/src/lib/models.ts b/src/lib/models.ts
index 2c2b71e..90e9321 100644
--- a/src/lib/models.ts
+++ b/src/lib/models.ts
@@ -83,6 +83,7 @@ export interface IServer extends Document {
autoRestart: boolean
backupSchedule: string | null
backupRetention: number
+ admins: mongoose.Types.ObjectId[]
createdBy: mongoose.Types.ObjectId
createdAt: Date
updatedAt: Date
@@ -113,6 +114,7 @@ const serverSchema = new Schema(
autoRestart: { type: Boolean, default: true },
backupSchedule: { type: String, default: null },
backupRetention: { type: Number, default: 5 },
+ admins: [{ type: Schema.Types.ObjectId, ref: 'User', default: [] }],
createdBy: { type: Schema.Types.ObjectId, ref: 'User', required: true },
},
{ timestamps: true }
diff --git a/src/types/server.ts b/src/types/server.ts
index ddb97a8..8801c45 100644
--- a/src/types/server.ts
+++ b/src/types/server.ts
@@ -25,6 +25,7 @@ export interface Server {
autoRestart: boolean
backupSchedule: string | null
backupRetention: number
+ admins: string[]
createdBy: string
createdAt: string
updatedAt: string