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:
13
schemas/minecraftservices/minecraft/profile.js
Normal file
13
schemas/minecraftservices/minecraft/profile.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
schemas/minecraftservices/minecraft/profile/capes/active.js
Normal file
28
schemas/minecraftservices/minecraft/profile/capes/active.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
DELETE: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
},
|
||||
PUT: {
|
||||
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({
|
||||
capeId: z.string().uuid({ message: "Invalid Cape UUID." })
|
||||
}),
|
||||
error: {
|
||||
code: 400,
|
||||
message: "profile does not own cape",
|
||||
error: "IllegalArgumentException"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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" })
|
||||
}),
|
||||
body: z.array(z.string().trim().min(1))
|
||||
.min(1, { message: "RequestPayload is an empty array." })
|
||||
.max(10, { message: "RequestPayload has more than 10 elements." }),
|
||||
error: {
|
||||
code: 400,
|
||||
error: "CONSTRAINT_VIOLATION",
|
||||
errorMessage: "size must be between 1 and 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
params: z.object({
|
||||
username: z.string().min(1)
|
||||
}),
|
||||
error: {
|
||||
code: 404,
|
||||
message: "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
22
schemas/minecraftservices/minecraft/profile/name/[name].js
Normal file
22
schemas/minecraftservices/minecraft/profile/name/[name].js
Normal file
@@ -0,0 +1,22 @@
|
||||
const z = require("zod")
|
||||
|
||||
const nameSchema = z.string()
|
||||
.min(1)
|
||||
.max(16)
|
||||
.regex(/^[a-zA-Z0-9_]+$/, { message: "Name can only contain alphanumeric characters and underscores." });
|
||||
|
||||
module.exports = {
|
||||
PUT: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
params: z.object({
|
||||
name: nameSchema
|
||||
}),
|
||||
error: {
|
||||
code: 400,
|
||||
error: "CONSTRAINT_VIOLATION",
|
||||
errorMessage: "Invalid profile name"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
const z = require("zod")
|
||||
|
||||
const nameSchema = z.string()
|
||||
.min(1)
|
||||
.max(16)
|
||||
.regex(/^[a-zA-Z0-9_]+$/, { message: "Name can only contain alphanumeric characters and underscores." });
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
params: z.object({
|
||||
name: nameSchema
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
schemas/minecraftservices/minecraft/profile/namechange.js
Normal file
13
schemas/minecraftservices/minecraft/profile/namechange.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
schemas/minecraftservices/minecraft/profile/skins.js
Normal file
24
schemas/minecraftservices/minecraft/profile/skins.js
Normal file
@@ -0,0 +1,24 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
schemas/minecraftservices/minecraft/profile/skins/active.js
Normal file
13
schemas/minecraftservices/minecraft/profile/skins/active.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
DELETE: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
schemas/minecraftservices/player/attributes.js
Normal file
29
schemas/minecraftservices/player/attributes.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
},
|
||||
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({
|
||||
profanityFilterPreferences: z.object({
|
||||
profanityFilterOn: z.boolean({ required_error: "profanityFilterOn is required" })
|
||||
})
|
||||
}),
|
||||
error: {
|
||||
code: 400,
|
||||
message: "Invalid attributes format."
|
||||
}
|
||||
}
|
||||
}
|
||||
13
schemas/minecraftservices/player/certificates.js
Normal file
13
schemas/minecraftservices/player/certificates.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
POST: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
schemas/minecraftservices/privacy/blocklist.js
Normal file
13
schemas/minecraftservices/privacy/blocklist.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
schemas/minecraftservices/privacy/blocklist/[uuid].js
Normal file
28
schemas/minecraftservices/privacy/blocklist/[uuid].js
Normal file
@@ -0,0 +1,28 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
PUT: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
params: z.object({
|
||||
uuid: z.string().uuid({ message: "Invalid UUID." })
|
||||
}),
|
||||
error: {
|
||||
code: 400,
|
||||
message: "Invalid UUID"
|
||||
}
|
||||
},
|
||||
DELETE: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
params: z.object({
|
||||
uuid: z.string().uuid({ message: "Invalid UUID." })
|
||||
}),
|
||||
error: {
|
||||
code: 400,
|
||||
message: "Invalid UUID"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
schemas/minecraftservices/privileges.js
Normal file
29
schemas/minecraftservices/privileges.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"authorization": z.string().min(1, { message: "Authorization header is required." })
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
},
|
||||
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({
|
||||
profanityFilterPreferences: z.object({
|
||||
profanityFilterOn: z.boolean({ required_error: "profanityFilterOn is required" })
|
||||
}).optional()
|
||||
}),
|
||||
error: {
|
||||
code: 401,
|
||||
message: "Unauthorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user