Yggdrasil/services/adminService.js
azures04 c5b6f6c107 Add Discord OAuth2 account linking and login support
Introduces Discord OAuth2 integration for account association and login, including new routes for linking, unlinking, and authenticating via Discord. Adds supporting services, repositories, and schema validation for the OAuth2 flow. Refactors database schema and queries for consistency, and updates dependencies to include required OAuth2 libraries.
2026-01-11 21:03:12 +01:00

151 lines
4.7 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, "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 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, "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
}