172 lines
5.3 KiB
JavaScript
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
|
|
} |