Compare commits
30 Commits
6cf822e603
...
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 | |||
| 21fd655a1f | |||
| 074a5dc04b |
@@ -14,7 +14,7 @@ async function register(email, username, password) {
|
|||||||
})
|
})
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
if (json.code != 200) {
|
if (json.code != 200) {
|
||||||
return iziToast.error({ title: json.error, message: json.message })
|
return iziToast.error({ title: json.error || "Erreur", message: json.message || "Erreur inconnue" })
|
||||||
} else {
|
} else {
|
||||||
console.log(json)
|
console.log(json)
|
||||||
return iziToast.success({ title: "Succès", message: json.message })
|
return iziToast.success({ title: "Succès", message: json.message })
|
||||||
@@ -22,8 +22,8 @@ async function register(email, username, password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
const email = document.querySelector("#email")
|
const email = document.querySelector("#email").value
|
||||||
const username = document.querySelector("#username")
|
const username = document.querySelector("#username").value
|
||||||
const password = document.querySelector("#password")
|
const password = document.querySelector("#password").value
|
||||||
register(email, username, password)
|
register(email, username, password)
|
||||||
})
|
})
|
||||||
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"])
|
logger.log(`defaults skins (steve, alex) ready`, ["MariaDB", "yellow"])
|
||||||
|
|
||||||
await conn.query(`DROP TRIGGER IF EXISTS unique_active_skin`)
|
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`)
|
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 database = require("../modules/database")
|
||||||
const { DefaultError } = require("../errors/errors")
|
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) {
|
async function addPropertyToPlayer(key, value, uuid) {
|
||||||
try {
|
try {
|
||||||
const sql = `
|
const sql = `
|
||||||
@@ -626,19 +656,21 @@ async function getNameHistory(uuid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setSkin(uuid, hash, variant) {
|
async function setSkin(uuid, hash, variant) {
|
||||||
const insertSql = `
|
try {
|
||||||
|
const resetSql = `UPDATE playersSkins SET isSelected = 0 WHERE playerUuid = ?`
|
||||||
|
await database.query(resetSql, [uuid])
|
||||||
|
|
||||||
|
const upsertSql = `
|
||||||
INSERT INTO playersSkins (playerUuid, assetHash, variant, isSelected)
|
INSERT INTO playersSkins (playerUuid, assetHash, variant, isSelected)
|
||||||
VALUES (?, ?, ?, 1)
|
VALUES (?, ?, ?, 1)
|
||||||
ON DUPLICATE KEY UPDATE isSelected = 1, variant = ?
|
ON DUPLICATE KEY UPDATE isSelected = 1, variant = VALUES(variant)
|
||||||
`
|
`
|
||||||
await database.query(insertSql, [uuid, hash, variant, variant])
|
await database.query(upsertSql, [uuid, hash, variant.toUpperCase()])
|
||||||
const updateSql = `
|
|
||||||
UPDATE playersSkins
|
|
||||||
SET isSelected = 0
|
|
||||||
WHERE playerUuid = ? AND assetHash != ?
|
|
||||||
`
|
|
||||||
await database.query(updateSql, [uuid, hash])
|
|
||||||
return true
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
return utils.handleDBError(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updatePassword(uuid, hashedPassword) {
|
async function updatePassword(uuid, hashedPassword) {
|
||||||
@@ -704,6 +736,8 @@ async function deleteTexture(hash) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
setSkin,
|
setSkin,
|
||||||
banUser,
|
banUser,
|
||||||
|
getSkins,
|
||||||
|
getCapes,
|
||||||
showCape,
|
showCape,
|
||||||
hideCape,
|
hideCape,
|
||||||
resetSkin,
|
resetSkin,
|
||||||
|
|||||||
@@ -6,11 +6,19 @@ const authService = require("../../../../../services/authService")
|
|||||||
router.delete("/", async (req, res) => {
|
router.delete("/", async (req, res) => {
|
||||||
const player = await authService.verifyAccessToken({ accessToken: req.headers.authorization.replace("Bearer", "").trim() })
|
const player = await authService.verifyAccessToken({ accessToken: req.headers.authorization.replace("Bearer", "").trim() })
|
||||||
await userService.hideCape(player.user.uuid)
|
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) => {
|
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)
|
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)])
|
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)])
|
const [skinsResult, capesResult] = await Promise.all([userService.getSkins(player.user.uuid), userService.getCapes(player.user.uuid)])
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
id: player.uuid.replace(/-/g, ""),
|
id: player.user.uuid.replace(/-/g, ""),
|
||||||
name: player.user.username,
|
name: player.user.username,
|
||||||
skins: skinsResult.data || [],
|
skins: skinsResult.data || [],
|
||||||
capes: capesResult.data || []
|
capes: capesResult.data || []
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const express = require("express")
|
const express = require("express")
|
||||||
const authService = require("../../../../../services/authService")
|
const authService = require("../../../../../services/authService")
|
||||||
|
const userService = require("../../../../../services/userService")
|
||||||
const { DefaultError, ServiceError } = require("../../../../../errors/errors")
|
const { DefaultError, ServiceError } = require("../../../../../errors/errors")
|
||||||
const router = express.Router({ mergeParams: true })
|
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 player = await authService.verifyAccessToken({ accessToken: req.headers.authorization.replace("Bearer", "").trim() })
|
||||||
const newName = req.params.name
|
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 skinsResult = await userService.getSkins({ uuid: player.user.uuid })
|
||||||
const capesResult = await userService.getCapes({ uuid: player.uuid })
|
const capesResult = await userService.getCapes({ uuid: player.user.uuid })
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
id: player.uuid.replace(/-/g, ""),
|
id: player.user.uuid.replace(/-/g, ""),
|
||||||
name: newName,
|
name: newName,
|
||||||
skins: skinsResult.data || [],
|
skins: skinsResult.data || [],
|
||||||
capes: capesResult.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()
|
certificates.setupKeys()
|
||||||
|
|
||||||
app.use(hpp())
|
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(cors({ origin: "*" }))
|
||||||
|
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const oauth2Repository = require("../repositories/oauth2Repository")
|
|||||||
const userService = require("./userService")
|
const userService = require("./userService")
|
||||||
const authService = require("./authService")
|
const authService = require("./authService")
|
||||||
const { StateTypes, Scopes, PromptTypes, ResponseCodeTypes } = require("@mgalacyber/discord-oauth2")
|
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({
|
const oauth2_association = new DiscordOAuth2({
|
||||||
clientId: process.env.DISCORD_CLIENT_ID,
|
clientId: process.env.DISCORD_CLIENT_ID,
|
||||||
@@ -118,7 +118,7 @@ async function handleLoginCallback(provider, code, requestUser) {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code == 404) {
|
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
|
if (error instanceof DefaultError) throw error
|
||||||
throw new DefaultError(500, `${provider} authentication failed: + ${error.message}`)
|
throw new DefaultError(500, `${provider} authentication failed: + ${error.message}`)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const bcrypt = require("bcryptjs")
|
|||||||
const logger = require("../modules/logger")
|
const logger = require("../modules/logger")
|
||||||
const crypto = require("node:crypto")
|
const crypto = require("node:crypto")
|
||||||
const ssrfcheck = require("ssrfcheck")
|
const ssrfcheck = require("ssrfcheck")
|
||||||
|
const authService = require("./authService")
|
||||||
const certsManager = require("../modules/certificatesManager")
|
const certsManager = require("../modules/certificatesManager")
|
||||||
const userRepository = require("../repositories/userRepository")
|
const userRepository = require("../repositories/userRepository")
|
||||||
const { DefaultError } = require("../errors/errors")
|
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 TEMP_DIR = path.join(process.cwd(), "data", "temp")
|
||||||
const TEXTURES_DIR = path.join(process.cwd(), "data", "textures")
|
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) {
|
async function getPlayerProperties(uuid) {
|
||||||
try {
|
try {
|
||||||
const result = await userRepository.getPlayerProperties(uuid)
|
const result = await userRepository.getPlayerProperties(uuid)
|
||||||
@@ -456,8 +493,7 @@ async function uploadSkin(uuid, fileObject, variant) {
|
|||||||
const existingTexture = await userRepository.getTextureByHash(hash)
|
const existingTexture = await userRepository.getTextureByHash(hash)
|
||||||
|
|
||||||
if (!existingTexture) {
|
if (!existingTexture) {
|
||||||
const subDir = hash.substring(0, 2)
|
const targetDir = path.join(TEXTURES_DIR)
|
||||||
const targetDir = path.join(TEXTURES_DIR, subDir)
|
|
||||||
const targetPath = path.join(targetDir, hash)
|
const targetPath = path.join(targetDir, hash)
|
||||||
|
|
||||||
await fs.mkdir(targetDir, { recursive: true })
|
await fs.mkdir(targetDir, { recursive: true })
|
||||||
@@ -529,8 +565,10 @@ async function removeCape(uuid, hash) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
banUser,
|
banUser,
|
||||||
|
getCapes,
|
||||||
showCape,
|
showCape,
|
||||||
hideCape,
|
hideCape,
|
||||||
|
getSkins,
|
||||||
unbanUser,
|
unbanUser,
|
||||||
resetSkin,
|
resetSkin,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
|
|||||||
Reference in New Issue
Block a user