Add validation schemas and improve texture handling

Introduces zod-based validation schemas for Minecraft and Mojang API endpoints. Refactors texture route to support hash-based file serving and removes the old static texture route. Updates database schema for player properties and adds an event to clean expired certificates. Improves ValidationError formatting, adjusts skin/cape URL construction, and adds SSRF protection for skin uploads.
This commit is contained in:
2025-12-28 09:02:10 +01:00
parent 1fe46a03fd
commit 5cfadfd7ac
29 changed files with 490 additions and 24 deletions

View File

@@ -1,14 +1,52 @@
const DefaultError = require("./DefaultError")
const YggdrasilError = require("./YggdrasilError")
const SessionError = require("./SessionError")
const ServiceError = require("./ServiceError")
const logger = require("../modules/logger")
class ValidationError extends DefaultError {
constructor(zodResult, config = {}, context = {}) {
const formattedErrors = zodResult.error.issues.map(e => ({
field: e.path.join("."),
message: e.message
}))
constructor(result, config = {}, context = {}) {
let formattedErrors = []
if (result && result.error && Array.isArray(result.error.issues)) {
formattedErrors = result.error.issues.flatMap(issue => {
if (issue.code === "unrecognized_keys") {
return issue.keys.map(key => ({
field: [...issue.path, key].join("."),
message: "Field not allowed"
}))
}
if (issue.code === "invalid_union") {
return {
field: issue.path.join("."),
message: "Invalid input format (union mismatch)"
}
}
return {
field: issue.path.join("."),
message: issue.message
}
})
}
else if (result instanceof Error) {
formattedErrors = [{
field: "global",
message: result.message
}]
}
else if (typeof result === "string") {
formattedErrors = [{
field: "global",
message: result
}]
}
else {
formattedErrors = [{
field: "unknown",
message: "Unknown validation error"
}]
}
const message = config.message || "Validation failed"
const statusCode = config.code || 400
@@ -54,11 +92,27 @@ class ValidationError extends DefaultError {
return err.serialize()
}
return {
if (this.config.errorFormat === "ServiceError") {
const err = new ServiceError(
this.code,
this.context.path || "",
this.config.errorName || "ValidationException",
this.message,
this.formattedErrors
)
return err.serialize()
}
const response = {
code: this.code,
message: this.message,
errors: this.formattedErrors
}
if (this.cause) {
response.cause = this.cause
}
return response
}
}