Add admin API, permissions, and player management routes

Introduces admin database tables, repository, and service for managing administrators and permissions. Adds new admin routes for banning players, managing cosmetics (capes), changing player passwords and usernames, and handling player textures. Updates user and session services to support admin actions and permission checks. Adds related schema validation for new endpoints.
This commit is contained in:
2026-01-05 04:44:56 +01:00
parent da8ab9d488
commit 439094013d
20 changed files with 628 additions and 14 deletions

View File

@@ -0,0 +1,131 @@
const logger = require("../modules/logger")
const database = require("../modules/database")
const { DefaultError } = require("../errors/errors")
async function getAdminById(id) {
try {
const sql = "SELECT id, username, createdAt FROM api_administrators WHERE id = ?"
const rows = await database.query(sql, [id])
return rows[0] || null
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function createAdmin(username, hashedPassword) {
try {
const sql = "INSERT INTO api_administrators (username, password) VALUES (?, ?)"
const result = await database.query(sql, [username, hashedPassword])
if (result.affectedRows > 0) {
return { code: 200, id: result.insertId, username }
} else {
throw new DefaultError(500, "Failed to create administrator.")
}
} catch (error) {
if (error.code === "ER_DUP_ENTRY") {
throw new DefaultError(409, "Administrator username already exists.")
}
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function hasPermission(adminId, permissionKey) {
try {
const sql = `
SELECT COUNT(*) as count
FROM api_administrators_permissions
WHERE administrator_id = ? AND permission_key = ?
`
const rows = await database.query(sql, [adminId, permissionKey])
return rows[0].count === 1
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function assignPermission(adminId, permissionKey) {
try {
const sql = "INSERT INTO api_administrators_permissions (administrator_id, permission_key) VALUES (?, ?)"
const result = await database.query(sql, [adminId, permissionKey])
return result.affectedRows > 0
} catch (error) {
if (error.code === "ER_DUP_ENTRY") return true
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function revokePermission(adminId, permissionKey) {
try {
const sql = "DELETE FROM api_administrators_permissions WHERE administrator_id = ? AND permission_key = ?"
const result = await database.query(sql, [adminId, permissionKey])
return result.affectedRows > 0
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function getAdminPermissions(adminId) {
try {
const sql = `
SELECT permission_key
FROM api_administrators_permissions
WHERE administrator_id = ?
`
const rows = await database.query(sql, [adminId])
return rows.map(r => r.permission_key)
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function updateAdminPassword(adminId, newHashedPassword) {
try {
const sql = "UPDATE api_administrators SET password = ? WHERE id = ?"
const result = await database.query(sql, [newHashedPassword, adminId])
if (result.affectedRows > 0) {
return {
code: 200,
message: "Password updated successfully."
}
} else {
throw new DefaultError(404, "Administrator not found.")
}
} catch (error) {
if (error instanceof DefaultError) throw error
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function getAdminByUsername(username) {
try {
const sql = "SELECT id, username, password, createdAt FROM api_administrators WHERE username = ?"
const rows = await database.query(sql, [username])
return rows[0] || null
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
module.exports = {
createAdmin,
getAdminById,
hasPermission,
assignPermission,
revokePermission,
getAdminByUsername,
getAdminPermissions,
updateAdminPassword
}

View File

@@ -145,7 +145,8 @@ async function getServerSession(uuid, serverId) {
const sql = `
SELECT ip
FROM serverSessions
WHERE uuid = ? AND serverId = ?
WHERE uuid = ? AND serverId = ?
AND createdAt > (NOW() - INTERVAL 30 SECOND)
`
const rows = await database.query(sql, [uuid, serverId])
const session = rows[0]

View File

@@ -660,6 +660,72 @@ async function setSkin(uuid, hash, variant) {
return true
}
async function updatePassword(uuid, hashedPassword) {
try {
const sql = "UPDATE players SET password = ? WHERE uuid = ?"
const result = await database.query(sql, [hashedPassword, uuid])
if (result.affectedRows > 0) {
return { code: 200, message: "Password updated successfully" }
} else {
throw new DefaultError(404, "User not found")
}
} catch (error) {
if (error instanceof DefaultError) throw error
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function addCapeToPlayer(uuid, hash) {
try {
const sql = `
INSERT INTO playersCapes (playerUuid, assetHash, isSelected)
VALUES (?, ?, 0)
`
const result = await database.query(sql, [uuid, hash])
if (result.affectedRows > 0) {
return { code: 200, message: "Cape accordée au joueur." }
}
throw new DefaultError(500, "Erreur lors de l'attribution de la cape.")
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
throw new DefaultError(409, "Le joueur possède déjà cette cape.")
}
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function removeCapeFromPlayer(uuid, hash) {
try {
const sql = "DELETE FROM playersCapes WHERE playerUuid = ? AND assetHash = ?"
const result = await database.query(sql, [uuid, hash])
if (result.affectedRows > 0) {
return { code: 200, message: "Cape retirée du joueur." }
} else {
throw new DefaultError(404, "Le joueur ne possède pas cette cape.")
}
} catch (error) {
if (error instanceof DefaultError) throw error
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
async function deleteTexture(hash) {
try {
const sql = "DELETE FROM textures WHERE hash = ?"
const result = await database.query(sql, [hash])
return result.affectedRows > 0
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
module.exports = {
setSkin,
banUser,
@@ -669,14 +735,17 @@ module.exports = {
isBlocked,
unbanUser,
blockPlayer,
deleteTexture,
createTexture,
getPlayerBans,
getPlayerMeta,
unblockPlayer,
changeUsername,
getNameHistory,
updatePassword,
getBlockedUuids,
getUsersByNames,
addCapeToPlayer,
getPlayerActions,
getTextureByUuid,
getTextureByHash,
@@ -690,6 +759,7 @@ module.exports = {
getPlayerProperties,
removeProfileAction,
addPropertyToPlayer,
removeCapeFromPlayer,
getProfileByUsername,
getPlayerPreferences,
getPlayerCertificate,