Compare commits
28 Commits
21fd655a1f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e242d78864 | |||
| 33d54e655a | |||
| 39a566a2f5 | |||
| 66b3268f8e | |||
| 9db4d62d78 | |||
| 492f012519 | |||
| c5ef3d8181 | |||
| bf17261fdf | |||
| bb7b2328ab | |||
| b6ad724602 | |||
| dbcb436c9f | |||
| aea0b7b016 | |||
| 11f930c3d8 | |||
| b82c06165a | |||
| 48ea9f708b | |||
| 3f64c2c897 | |||
| c1dbfc58aa | |||
| 65f2f26325 | |||
| f9270c1f9c | |||
| 6f3231aee7 | |||
| 4a0fcf65f3 | |||
| cc8e581cbe | |||
| b4ae5fa4d9 | |||
| 7018e8c497 | |||
| 8e0a1ab673 | |||
| e089957db7 | |||
| c8812c5153 | |||
| 66db52e7c8 |
25
data/static/success.html
Normal file
25
data/static/success.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/hung1001/font-awesome-pro@4cac1a6/css/all.css">
|
||||
<title>Registration</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>
|
||||
<i class="fad fa-shield-alt"></i>
|
||||
Lentia – Yggdrasil
|
||||
</h1>
|
||||
<h3>Authentification réussie</h3>
|
||||
<hr>
|
||||
<div>
|
||||
<p>
|
||||
Vous pouvez dès à présent fermer cette page et retourner au launcher
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -242,20 +242,6 @@ async function setupDatabase() {
|
||||
logger.log(`defaults skins (steve, alex) ready`, ["MariaDB", "yellow"])
|
||||
|
||||
await conn.query(`DROP TRIGGER IF EXISTS unique_active_skin`)
|
||||
await conn.query(`
|
||||
CREATE TRIGGER unique_active_skin
|
||||
AFTER UPDATE ON playersSkins
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF NEW.isSelected = 1 THEN
|
||||
UPDATE playersSkins
|
||||
SET isSelected = 0
|
||||
WHERE playerUuid = NEW.playerUuid
|
||||
AND assetHash != NEW.assetHash;
|
||||
END IF;
|
||||
END;
|
||||
`)
|
||||
logger.log(`${"unique_active_skin".bold} trigger ready`, ["MariaDB", "yellow"])
|
||||
|
||||
await conn.query(`DROP TRIGGER IF EXISTS unique_active_cape`)
|
||||
|
||||
|
||||
@@ -3,6 +3,36 @@ const crypto = require("node:crypto")
|
||||
const database = require("../modules/database")
|
||||
const { DefaultError } = require("../errors/errors")
|
||||
|
||||
async function getSkins(uuid) {
|
||||
try {
|
||||
const sql = `
|
||||
SELECT t.uuid as textureUuid, t.hash, t.url, ps.variant, ps.isSelected
|
||||
FROM playersSkins ps
|
||||
JOIN textures t ON ps.assetHash = t.hash
|
||||
WHERE ps.playerUuid = ?
|
||||
`
|
||||
const rows = await database.query(sql, [uuid])
|
||||
return rows
|
||||
} catch (error) {
|
||||
return utils.handleDBError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function getCapes(uuid) {
|
||||
try {
|
||||
const sql = `
|
||||
SELECT t.uuid as textureUuid, t.hash, t.url, t.alias, pc.isSelected
|
||||
FROM playersCapes pc
|
||||
JOIN textures t ON pc.assetHash = t.hash
|
||||
WHERE pc.playerUuid = ?
|
||||
`
|
||||
const rows = await database.query(sql, [uuid])
|
||||
return rows
|
||||
} catch (error) {
|
||||
return utils.handleDBError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function addPropertyToPlayer(key, value, uuid) {
|
||||
try {
|
||||
const sql = `
|
||||
@@ -626,19 +656,21 @@ async function getNameHistory(uuid) {
|
||||
}
|
||||
|
||||
async function setSkin(uuid, hash, variant) {
|
||||
const insertSql = `
|
||||
INSERT INTO playersSkins (playerUuid, assetHash, variant, isSelected)
|
||||
VALUES (?, ?, ?, 1)
|
||||
ON DUPLICATE KEY UPDATE isSelected = 1, variant = ?
|
||||
`
|
||||
await database.query(insertSql, [uuid, hash, variant, variant])
|
||||
const updateSql = `
|
||||
UPDATE playersSkins
|
||||
SET isSelected = 0
|
||||
WHERE playerUuid = ? AND assetHash != ?
|
||||
`
|
||||
await database.query(updateSql, [uuid, hash])
|
||||
return true
|
||||
try {
|
||||
const resetSql = `UPDATE playersSkins SET isSelected = 0 WHERE playerUuid = ?`
|
||||
await database.query(resetSql, [uuid])
|
||||
|
||||
const upsertSql = `
|
||||
INSERT INTO playersSkins (playerUuid, assetHash, variant, isSelected)
|
||||
VALUES (?, ?, ?, 1)
|
||||
ON DUPLICATE KEY UPDATE isSelected = 1, variant = VALUES(variant)
|
||||
`
|
||||
await database.query(upsertSql, [uuid, hash, variant.toUpperCase()])
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
return utils.handleDBError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePassword(uuid, hashedPassword) {
|
||||
@@ -704,6 +736,8 @@ async function deleteTexture(hash) {
|
||||
module.exports = {
|
||||
setSkin,
|
||||
banUser,
|
||||
getSkins,
|
||||
getCapes,
|
||||
showCape,
|
||||
hideCape,
|
||||
resetSkin,
|
||||
|
||||
@@ -6,11 +6,19 @@ const authService = require("../../../../../services/authService")
|
||||
router.delete("/", async (req, res) => {
|
||||
const player = await authService.verifyAccessToken({ accessToken: req.headers.authorization.replace("Bearer", "").trim() })
|
||||
await userService.hideCape(player.user.uuid)
|
||||
return res.status(200).send()
|
||||
|
||||
const [skinsResult, capesResult] = await Promise.all([userService.getSkins(player.user.uuid), userService.getCapes(player.user.uuid)])
|
||||
|
||||
return res.status(200).json({
|
||||
id: player.user.uuid.replace(/-/g, ""),
|
||||
name: player.user.username,
|
||||
skins: skinsResult.data || [],
|
||||
capes: capesResult.data || []
|
||||
})
|
||||
})
|
||||
|
||||
router.put("/", async (req, res) => {
|
||||
const player = await authService.verifyAccessToken(req.headers.authorization)
|
||||
const player = await authService.verifyAccessToken({ accessToken: req.headers.authorization.replace("Bearer", "").trim() })
|
||||
|
||||
await userService.showCape(player.user.uuid, req.body.capeId)
|
||||
const [skinsResult, capesResult] = await Promise.all([userService.getSkins(player.user.uuid), userService.getCapes(player.user.uuid)])
|
||||
|
||||
@@ -8,7 +8,7 @@ router.get("/", async (req, res) => {
|
||||
const [skinsResult, capesResult] = await Promise.all([userService.getSkins(player.user.uuid), userService.getCapes(player.user.uuid)])
|
||||
|
||||
return res.status(200).json({
|
||||
id: player.uuid.replace(/-/g, ""),
|
||||
id: player.user.uuid.replace(/-/g, ""),
|
||||
name: player.user.username,
|
||||
skins: skinsResult.data || [],
|
||||
capes: capesResult.data || []
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require("express")
|
||||
const authService = require("../../../../../services/authService")
|
||||
const userService = require("../../../../../services/userService")
|
||||
const { DefaultError, ServiceError } = require("../../../../../errors/errors")
|
||||
const router = express.Router({ mergeParams: true })
|
||||
|
||||
@@ -21,13 +22,13 @@ router.put("/", async (req, res) => {
|
||||
const player = await authService.verifyAccessToken({ accessToken: req.headers.authorization.replace("Bearer", "").trim() })
|
||||
const newName = req.params.name
|
||||
|
||||
await userService.changeUsername(player.uuid, newName)
|
||||
await userService.changeUsername(player.user.uuid, newName)
|
||||
|
||||
const skinsResult = await userService.getSkins({ uuid: player.uuid })
|
||||
const capesResult = await userService.getCapes({ uuid: player.uuid })
|
||||
const skinsResult = await userService.getSkins({ uuid: player.user.uuid })
|
||||
const capesResult = await userService.getCapes({ uuid: player.user.uuid })
|
||||
|
||||
return res.status(200).json({
|
||||
id: player.uuid.replace(/-/g, ""),
|
||||
id: player.user.uuid.replace(/-/g, ""),
|
||||
name: newName,
|
||||
skins: skinsResult.data || [],
|
||||
capes: capesResult.data || []
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
POST: {
|
||||
headers: z.object({
|
||||
"content-type": z.string()
|
||||
.regex(/application\/json/i, { message: "Content-Type must be application/json" }),
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
body: z.object({
|
||||
variant: z.enum(["classic", "slim"], {
|
||||
errorMap: () => ({ message: "Variant must be 'classic' or 'slim'." })
|
||||
}),
|
||||
url: z.string()
|
||||
.url({ message: "Invalid URL format." })
|
||||
.max(2048, { message: "URL is too long." })
|
||||
}),
|
||||
error: {
|
||||
code: 400,
|
||||
message: "Invalid skin URL or variant.",
|
||||
error: "IllegalArgumentException"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
server.js
15
server.js
@@ -21,7 +21,20 @@ databaseGlobals.setupDatabase()
|
||||
certificates.setupKeys()
|
||||
|
||||
app.use(hpp())
|
||||
app.use(helmet())
|
||||
app.use(helmet({
|
||||
crossOriginResourcePolicy: { policy: "cross-origin" },
|
||||
crossOriginEmbedderPolicy: false,
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "https://cdnjs.cloudflare.com", "'unsafe-inline'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"],
|
||||
fontSrc: ["'self'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"],
|
||||
connectSrc: ["'self'", "https://yggdrasil.azures.fr"],
|
||||
imgSrc: ["'self'", "data:"],
|
||||
},
|
||||
}
|
||||
}))
|
||||
app.use(cors({ origin: "*" }))
|
||||
|
||||
app.use(express.json())
|
||||
|
||||
@@ -3,7 +3,7 @@ const oauth2Repository = require("../repositories/oauth2Repository")
|
||||
const userService = require("./userService")
|
||||
const authService = require("./authService")
|
||||
const { StateTypes, Scopes, PromptTypes, ResponseCodeTypes } = require("@mgalacyber/discord-oauth2")
|
||||
const { DefaultError } = require("../errors/errors")
|
||||
const { DefaultError, YggdrasilError } = require("../errors/errors")
|
||||
|
||||
const oauth2_association = new DiscordOAuth2({
|
||||
clientId: process.env.DISCORD_CLIENT_ID,
|
||||
@@ -118,7 +118,7 @@ async function handleLoginCallback(provider, code, requestUser) {
|
||||
|
||||
} catch (error) {
|
||||
if (error.code == 404) {
|
||||
throw new DefaultError(404, `No ${provider} account linked to any player.`, "NotLinkedError")
|
||||
throw new YggdrasilError(404, "NotLinkedError", `No ${provider} account linked to any player.`)
|
||||
}
|
||||
if (error instanceof DefaultError) throw error
|
||||
throw new DefaultError(500, `${provider} authentication failed: + ${error.message}`)
|
||||
|
||||
@@ -5,6 +5,7 @@ const bcrypt = require("bcryptjs")
|
||||
const logger = require("../modules/logger")
|
||||
const crypto = require("node:crypto")
|
||||
const ssrfcheck = require("ssrfcheck")
|
||||
const authService = require("./authService")
|
||||
const certsManager = require("../modules/certificatesManager")
|
||||
const userRepository = require("../repositories/userRepository")
|
||||
const { DefaultError } = require("../errors/errors")
|
||||
@@ -13,6 +14,42 @@ const generateKeyPairAsync = util.promisify(crypto.generateKeyPair)
|
||||
const TEMP_DIR = path.join(process.cwd(), "data", "temp")
|
||||
const TEXTURES_DIR = path.join(process.cwd(), "data", "textures")
|
||||
|
||||
async function getSkins(uuid) {
|
||||
try {
|
||||
const rawSkins = await userRepository.getSkins(uuid)
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: rawSkins.map(r => ({
|
||||
id: r.textureUuid,
|
||||
state: r.isSelected == 1 ? "ACTIVE" : "INACTIVE",
|
||||
url: r.url,
|
||||
variant: r.variant || "CLASSIC"
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function getCapes(uuid) {
|
||||
try {
|
||||
const rawCapes = await userRepository.getCapes(uuid)
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: rawCapes.map(r => ({
|
||||
id: r.textureUuid,
|
||||
state: r.isSelected == 1 ? "ACTIVE" : "INACTIVE",
|
||||
url: r.url,
|
||||
alias: r.alias || "LentiaCustomCape"
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function getPlayerProperties(uuid) {
|
||||
try {
|
||||
const result = await userRepository.getPlayerProperties(uuid)
|
||||
@@ -456,8 +493,7 @@ async function uploadSkin(uuid, fileObject, variant) {
|
||||
const existingTexture = await userRepository.getTextureByHash(hash)
|
||||
|
||||
if (!existingTexture) {
|
||||
const subDir = hash.substring(0, 2)
|
||||
const targetDir = path.join(TEXTURES_DIR, subDir)
|
||||
const targetDir = path.join(TEXTURES_DIR)
|
||||
const targetPath = path.join(targetDir, hash)
|
||||
|
||||
await fs.mkdir(targetDir, { recursive: true })
|
||||
@@ -529,8 +565,10 @@ async function removeCape(uuid, hash) {
|
||||
|
||||
module.exports = {
|
||||
banUser,
|
||||
getCapes,
|
||||
showCape,
|
||||
hideCape,
|
||||
getSkins,
|
||||
unbanUser,
|
||||
resetSkin,
|
||||
isBlocked,
|
||||
|
||||
Reference in New Issue
Block a user