Yggdrasil/services/adminService.js
2026-01-18 23:47:29 +01:00

172 lines
5.3 KiB
JavaScript

const fs = require("node:fs").promises
const path = require("node:path")
const jwt = require("jsonwebtoken")
const crypto = require("node:crypto")
const bcrypt = require("bcryptjs")
const userRepository = require("../repositories/userRepository")
const adminRepository = require("../repositories/adminRepository")
const { DefaultError } = require("../errors/errors")
const ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || "udjJLGCOq7m3NmGpdVLJ@#"
async function registerAdmin(username, plainPassword, permissions = []) {
const hashedPassword = await bcrypt.hash(plainPassword, 10)
const result = await adminRepository.createAdmin(username, hashedPassword)
if (permissions.length > 0) {
for (const perm of permissions) {
await adminRepository.assignPermission(result.id, perm)
}
}
return { id: result.id, username, message: "Administrator successfully created." }
}
async function checkAdminAccess(adminId, requiredPermission) {
if (typeof adminId != "number" || !requiredPermission) {
throw new DefaultError(400, "Administrator ID or permission missing.")
}
return await adminRepository.hasPermission(adminId, requiredPermission)
}
async function changeAdminPassword(adminId, newPlainPassword) {
if (!newPlainPassword || newPlainPassword.length < 8) {
throw new DefaultError(400, "The password must contain at least 8 characters.")
}
const hashed = await bcrypt.hash(newPlainPassword, 10)
return await adminRepository.updateAdminPassword(adminId, hashed)
}
async function getAdminProfile(adminId) {
const admin = await adminRepository.getAdminById(adminId)
if (!admin) {
throw new DefaultError(404, "Administrator not found.")
}
const permissions = await adminRepository.getAdminPermissions(adminId)
return {
id: admin.id,
username: admin.username,
createdAt: admin.createdAt,
permissions: permissions
}
}
async function getAdminProfileByToken(accessToken) {
try {
const decoded = jwt.verify(accessToken, { complete: true, json: true })
return getAdminProfile(decoded.sub)
} catch (error) {
throw error
}
}
async function grantPermission(adminId, permissionKey) {
return await adminRepository.assignPermission(adminId, permissionKey)
}
async function revokePermission(adminId, permissionKey) {
return await adminRepository.revokePermission(adminId, permissionKey)
}
async function loginAdmin(username, password) {
const admin = await adminRepository.getAdminByUsername(username)
if (!admin) {
throw new DefaultError(403, "Invalid credentials.")
}
const isMatch = await bcrypt.compare(password, admin.password)
if (!isMatch) {
throw new DefaultError(403, "Invalid credentials.")
}
const token = jwt.sign(
{ id: admin.id, username: admin.username, type: "admin" },
ADMIN_JWT_SECRET,
{ expiresIn: "8h", subject: admin.id.toString(), issuer: "Yggdrasil" }
)
return { token }
}
function hasPermission(requiredPermission) {
return async (req, res, next) => {
try {
const authHeader = req.headers.authorization
if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new DefaultError(401, "Admin auth required.")
}
const token = authHeader.split(" ")[1]
const decoded = jwt.verify(token, ADMIN_JWT_SECRET)
if (decoded.type !== "admin") {
throw new DefaultError(403, "Invalid token.")
}
const hasAccess = await checkAdminAccess(decoded.id, requiredPermission)
if (!hasAccess) {
throw new DefaultError(403, `Missing permission : ${requiredPermission}`)
}
req.admin = decoded
next()
} catch (err) {
if (err.name === "JsonWebTokenError") {
return next(new DefaultError(401, "Invalid session."))
}
next(err)
}
}
}
async function uploadCape(fileObject, alias = null) {
const buffer = await fs.readFile(fileObject.path)
const hash = crypto.createHash("sha256").update(buffer).digest("hex")
const existing = await userRepository.getTextureByHash(hash)
if (existing) {
await fs.unlink(fileObject.path)
throw new DefaultError(409, "Cape already existing.")
}
const finalPath = path.join(process.cwd(), "data/textures", `${hash}`)
await fs.rename(fileObject.path, finalPath)
const textureUrl = `/texture/${hash}`
await userRepository.createTexture(crypto.randomUUID(), hash, "CAPE", textureUrl, alias)
return { hash, url: textureUrl }
}
async function deleteCape(hash) {
const success = await userRepository.deleteTexture(hash)
if (!success) throw new DefaultError(404, "Cape not found.")
return { message: "Texture removed." }
}
async function logPlayerAction(playerUuid, actionCode) {
return await adminRepository.addPlayerAction(playerUuid, actionCode)
}
module.exports = {
loginAdmin,
uploadCape,
deleteCape,
registerAdmin,
hasPermission,
getAdminProfile,
grantPermission,
logPlayerAction,
revokePermission,
checkAdminAccess,
changeAdminPassword,
getAdminProfileByToken
}