Add base project files including environment example, license, README, .gitignore, error classes, ESLint config, database modules, texture assets, repositories, routes, schemas, services, and server entry point. This establishes the foundational structure for a Yggdrasil-compatible REST API with modular error handling, database setup, and route organization.
145 lines
4.5 KiB
JavaScript
145 lines
4.5 KiB
JavaScript
const userRepository = require("../repositories/userRepository")
|
|
const adminRepository = require("../repositories/adminRepository")
|
|
const bcrypt = require("bcryptjs")
|
|
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: "Administrateur créé avec succès." }
|
|
}
|
|
|
|
async function checkAdminAccess(adminId, requiredPermission) {
|
|
if (!adminId || !requiredPermission) {
|
|
throw new DefaultError(400, "ID administrateur ou permission manquante.")
|
|
}
|
|
|
|
return await adminRepository.hasPermission(adminId, requiredPermission)
|
|
}
|
|
|
|
async function changeAdminPassword(adminId, newPlainPassword) {
|
|
if (!newPlainPassword || newPlainPassword.length < 6) {
|
|
throw new DefaultError(400, "Le mot de passe doit contenir au moins 6 caractères.")
|
|
}
|
|
|
|
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, "Administrateur introuvable.")
|
|
}
|
|
|
|
const permissions = await adminRepository.getAdminPermissions(adminId)
|
|
|
|
return {
|
|
id: admin.id,
|
|
username: admin.username,
|
|
createdAt: admin.createdAt,
|
|
permissions: permissions
|
|
}
|
|
}
|
|
|
|
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" }
|
|
)
|
|
|
|
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, "Authentification admin requise.")
|
|
}
|
|
|
|
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 adminService.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, "Cette cape existe déjà.")
|
|
|
|
const textureUrl = `/texture/${hash}`
|
|
await userRepository.createTexture(crypto.randomUUID(), hash, 'CAPE', textureUrl, alias)
|
|
|
|
return { hash, url: textureUrl }
|
|
}
|
|
|
|
async function deleteGlobalCape(hash) {
|
|
const success = await userRepository.deleteTexture(hash)
|
|
if (!success) throw new DefaultError(404, "Cape introuvable.")
|
|
|
|
return { message: "Texture supprimée globalement." }
|
|
}
|
|
|
|
module.exports = {
|
|
loginAdmin,
|
|
uploadCape,
|
|
registerAdmin,
|
|
getAdminProfile,
|
|
grantPermission,
|
|
revokePermission,
|
|
checkAdminAccess,
|
|
changeAdminPassword,
|
|
hasPermission,
|
|
} |