const fs = require("node:fs").promises const jwt = require("jsonwebtoken") 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) throw new DefaultError(409, "Cape already existing.") 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 }