Compare commits

..

No commits in common. "master" and "v2.1.2" have entirely different histories.

30 changed files with 1477 additions and 1633 deletions

2
.buildpacks Normal file
View File

@ -0,0 +1,2 @@
https://github.com/mojodna/heroku-buildpack-cairo.git
https://github.com/heroku/heroku-buildpack-nodejs.git

View File

@ -1,6 +0,0 @@
.*
*.md
Dockerfile
LICENSE
images/
node_modules/

21
.editorconfig Normal file
View File

@ -0,0 +1,21 @@
# We use EditorConfig to standardize settings between contributors
# See http://editorconfig.org for more info and plugin downloads
root = true
[*]
end_of_line = lf
insert_final_newline = false
trim_trailing_whitespace = true
[*.{js, json, yml}]
indent_style = space
indent_size = 2
charset = utf-8
[*.md]
trim_trailing_whitespace = false
[.gitignore]
# echo "filename" >> .gitignorre
insert_final_newline = true

24
.travis.yml Normal file
View File

@ -0,0 +1,24 @@
language: node_js
node_js:
- 12.16.1
sudo: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libcairo2-dev
- libjpeg8-dev
- libpango1.0-dev
- libgif-dev
- build-essential
- g++-4.8
script:
- npm run-script test-travis
env:
- TRAVIS=true CXX=g++-4.8
services:
- redis-server
cache:
directories:
- node_modules

View File

@ -1,35 +1,47 @@
FROM node:12-alpine AS builder
FROM node:12-alpine
RUN apk --no-cache add git python3 build-base redis cairo-dev pango-dev jpeg-dev giflib-dev
ARG AVATAR_MIN
ARG AVATAR_MAX
ARG AVATAR_DEFAULT
ARG RENDER_MIN
ARG RENDER_MAX
ARG RENDER_DEFAULT
ARG FACE_DIR
ARG HELM_DIR
ARG SKIN_DIR
ARG RENDER_DIR
ARG CAPE_DIR
ARG CACHE_LOCAL
ARG CACHE_BROWSER
ARG EPHEMERAL_STORAGE
ARG REDIS_URL
ARG PORT
ARG BIND
ARG EXTERNAL_HTTP_TIMEOUT
ARG DEBUG
ARG LOG_TIME
ARG SPONSOR_SIDE
ARG TOP_RIGHT
RUN adduser -D app
USER app
ENV NODE_ENV production
RUN apk --no-cache --virtual .build-deps add git python build-base
RUN apk --no-cache --virtual .canvas-deps add cairo-dev pango-dev jpeg-dev giflib-dev
RUN mkdir -p /crafatar/images/faces
RUN mkdir -p /crafatar/images/helms
RUN mkdir -p /crafatar/images/skins
RUN mkdir -p /crafatar/images/renders
RUN mkdir -p /crafatar/images/capes
VOLUME /crafatar/images
COPY package.json www.js config.js crafatar/
COPY lib/ crafatar/lib/
WORKDIR /crafatar
COPY --chown=app package.json package-lock.json /home/app/crafatar/
WORKDIR /home/app/crafatar
RUN npm install
COPY --chown=app . .
RUN mkdir -p images/faces images/helms images/skins images/renders images/capes
ARG VERBOSE_TEST
ARG DEBUG
RUN nohup redis-server & npm test
FROM node:12-alpine
RUN apk --no-cache add cairo pango jpeg giflib
RUN adduser -D app
USER app
RUN mkdir /home/app/crafatar
WORKDIR /home/app/crafatar
RUN mkdir -p images/faces images/helms images/skins images/renders images/capes
COPY --chown=app --from=builder /home/app/crafatar/node_modules/ node_modules/
COPY --chown=app package.json www.js config.js ./
COPY --chown=app lib/ lib/
VOLUME /home/app/crafatar/images
ENV NODE_ENV production
ENTRYPOINT ["npm", "start"]
EXPOSE 3000
ENTRYPOINT npm start

1
Procfile Normal file
View File

@ -0,0 +1 @@
web: npm start

View File

@ -1,8 +1,8 @@
# Crafatar
<img alt="logo" src="lib/public/logo.png" align="right" width="128px" height="128px">
# Crafatar [![travis](https://img.shields.io/travis/crafatar/crafatar/master.svg?style=flat-square)](https://travis-ci.org/crafatar/crafatar/) [![Coverage Status](https://img.shields.io/coveralls/crafatar/crafatar.svg?style=flat-square)](https://coveralls.io/r/crafatar/crafatar) [![Code Climate](https://img.shields.io/codeclimate/github/crafatar/crafatar.svg?style=flat-square)](https://codeclimate.com/github/crafatar/crafatar)
[![dependency status](https://img.shields.io/david/crafatar/crafatar.svg?style=flat-square)](https://david-dm.org/crafatar/crafatar) [![devDependency status](https://img.shields.io/david/dev/crafatar/crafatar.svg?style=flat-square)](https://david-dm.org/crafatar/crafatar#info=devDependencies) [![docs status](https://inch-ci.org/github/crafatar/crafatar.svg?branch=master&style=flat-square)](https://inch-ci.org/github/crafatar/crafatar)
[![travis](https://img.shields.io/travis/crafatar/crafatar/master.svg?style=flat-square)](https://travis-ci.org/crafatar/crafatar/) [![Coverage Status](https://img.shields.io/coveralls/crafatar/crafatar.svg?style=flat-square)](https://coveralls.io/r/crafatar/crafatar) [![Code Climate](https://img.shields.io/codeclimate/github/crafatar/crafatar.svg?style=flat-square)](https://codeclimate.com/github/crafatar/crafatar) [![dependency status](https://img.shields.io/david/crafatar/crafatar.svg?style=flat-square)](https://david-dm.org/crafatar/crafatar) [![devDependency status](https://img.shields.io/david/dev/crafatar/crafatar.svg?style=flat-square)](https://david-dm.org/crafatar/crafatar#info=devDependencies) [![docs status](https://inch-ci.org/github/crafatar/crafatar.svg?branch=master&style=flat-square)](https://inch-ci.org/github/crafatar/crafatar)
<img alt="logo" src="lib/public/logo.png" align="right">
<a href="https://crafatar.com">Crafatar</a> serves Minecraft avatars based on the skin for use in external applications.
Inspired by <a href="https://gravatar.com">Gravatar</a> (hence the name) and <a href="https://minotar.net">Minotar</a>.
@ -34,14 +34,6 @@ Please [visit the website](https://crafatar.com) for details.
# Installation
## Docker
```sh
docker network create crafatar
docker run --net crafatar -d --name redis redis
docker run --net crafatar -v crafatar-images:/home/app/crafatar/images -e REDIS_URL=redis://redis -p 3000:3000 crafatar/crafatar
```
## Manual
- Install [nodejs](https://nodejs.org/) 12 (LTS)
@ -52,6 +44,15 @@ docker run --net crafatar -v crafatar-images:/home/app/crafatar/images -e REDIS_
Crafatar is now available at http://0.0.0.0:3000.
## Docker
```sh
docker pull crafatar/crafatar
docker network create crafatar
docker run --net crafatar -d --name redis redis
docker run --net crafatar -v crafatar-images:/crafatar/images -e REDIS_URL=redis://redis -p 3000:3000 crafatar/crafatar
```
## Configration / Environment variables
See the `config.js` file.

30
app.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "Crafatar",
"description": "A blazing fast API for Minecraft faces!",
"repository": "https://github.com/crafatar/crafatar",
"keywords": [
"node",
"minecraft",
"avatar",
"redis"
],
"website": "https://crafatar.com/",
"env": {
"EPHEMERAL_STORAGE": {
"description": "Set to true if your storage is gone after deploying",
"required": false,
"value": true
}
},
"addons": [
"rediscloud"
],
"buildpacks": [
{
"url": "https://github.com/mojodna/heroku-buildpack-cairo.git"
},
{
"url": "https://github.com/heroku/heroku-buildpack-nodejs.git"
}
]
}

View File

@ -54,16 +54,12 @@ var config = {
log_time: process.env.LOG_TIME === "true",
// rate limit per second for outgoing requests to the Mojang session server
// requests exceeding this limit are skipped and considered failed
sessions_rate_limit: parseInt(process.env.SESSIONS_RATE_LIMIT)
sessions_rate_limit: parseInt(process.env.SESSIONS_RATE_LIMIT) || Infinity
},
sponsor: {
sidebar: process.env.SPONSOR_SIDE,
top_right: process.env.SPONSOR_TOP_RIGHT
},
endpoints: {
textures_url: process.env.TEXTURES_ENDPOINT || "https://textures.minecraft.net/texture/",
session_url: process.env.SESSION_ENDPOINT || "https://sessionserver.mojang.com/session/minecraft/profile/"
}
};
module.exports = config;

View File

@ -7,8 +7,8 @@ var skins = require("./skins");
var path = require("path");
var fs = require("fs");
// 0098cb60fa8e427cb299793cbd302c9a
var valid_user_id = /^[0-9a-fA-F]{32}$/; // uuid
// 0098cb60-fa8e-427c-b299-793cbd302c9a
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
var hash_pattern = /[0-9a-f]+$/;
// gets the hash from the textures.minecraft.net +url+
@ -122,22 +122,13 @@ var requests = {
cape: {}
};
var loginterval = setInterval(function(){
var skinreqs = Object.keys(requests.skin).length;
var capereqs = Object.keys(requests.cape).length;
if (skinreqs || capereqs) {
logging.log("Currently waiting for " + skinreqs + " skin requests and " + capereqs + " cape requests.");
}
}, 1000);
// add a request for +userId+ and +type+ to the queue
function push_request(userId, type, callback) {
function push_request(userId, type, fun) {
// avoid special properties (e.g. 'constructor')
var userId_safe = "!" + userId;
if (!requests[type][userId_safe]) {
requests[type][userId_safe] = [];
}
requests[type][userId_safe].push(callback);
requests[type][userId_safe].push(fun);
}
// calls back all queued requests that match userId and type
@ -171,6 +162,7 @@ function store_images(rid, userId, cache_details, type, callback) {
logging.debug(rid, "adding to request queue");
push_request(userId, type, callback);
} else {
// add request to the queue
push_request(userId, type, callback);
networking.get_profile(rid, userId, function(err, profile) {
@ -251,7 +243,7 @@ exp.get_image_hash = function(rid, userId, type, callback) {
// an error occured, but we have a cached hash
// (e.g. Mojang servers not reachable, using outdated hash)
// bump the TTL after hitting the rate limit
// when hitting the rate limit, let's pretend the request succeeded and bump the TTL
var ratelimited = store_err.code === "RATELIMIT";
cache.update_timestamp(rid, userId, !ratelimited, function(err2) {
callback(err2 || store_err, 4, cache_details && cached_hash, slim);
@ -332,7 +324,7 @@ function get_type(overlay, body) {
}
// handles creations of 3D renders
// callback: error, status, skin hash, image buffer
// callback: error, skin hash, image buffer
exp.get_render = function(rid, userId, scale, overlay, body, callback) {
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
if (!skin_hash) {
@ -358,7 +350,7 @@ exp.get_render = function(rid, userId, scale, overlay, body, callback) {
callback(null, 0, skin_hash, null);
} else {
fs.writeFile(renderpath, drawn_img, "binary", function(write_err) {
callback(write_err, status, skin_hash, drawn_img);
callback(write_err, 2, skin_hash, drawn_img);
});
}
});
@ -395,8 +387,4 @@ exp.get_cape = function(rid, userId, callback) {
});
};
exp.stoplog = function() {
clearInterval(loginterval);
}
module.exports = exp;

View File

@ -23,19 +23,15 @@ function log(level, args, logger) {
}
}
// log with INFO level
exp.log = function() {
log(" INFO", arguments);
};
// log with WARN level
exp.warn = function() {
log(" WARN", arguments, console.warn);
};
// log with ERROR level
exp.error = function() {
log("ERROR", arguments, console.error);
};
// log with DEBUG level if debug logging is enabled
if (config.server.debug_enabled) {
exp.debug = function() {
log("DEBUG", arguments);

View File

@ -5,15 +5,14 @@ var skins = require("./skins");
var http = require("http");
require("./object-patch");
var session_url = config.endpoints.session_url;
var textures_url = config.endpoints.textures_url;
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
var textures_url = "https://textures.minecraft.net/texture/";
// count requests made to session_url in the last 1000ms
var session_requests = [];
var exp = {};
// returns the amount of outgoing session requests made in the last 1000ms
function req_count() {
var index = session_requests.findIndex((i) => i >= Date.now() - 1000);
if (index >= 0) {
@ -23,7 +22,6 @@ function req_count() {
}
}
// deletes all entries in session_requests, should be called every 1000ms
exp.resetCounter = function() {
var count = req_count();
if (count) {
@ -41,10 +39,10 @@ exp.resetCounter = function() {
// callback: the body, response,
// and error buffer. get_from helper method is available
exp.get_from_options = function(rid, url, options, callback) {
var is_session_req = config.server.sessions_rate_limit && url.startsWith(session_url);
var session_req = url.startsWith(session_url);
// This is to prevent being blocked by CloudFront for exceeding the rate limit
if (is_session_req && req_count() >= config.server.sessions_rate_limit) {
if (session_req && req_count() >= config.server.sessions_rate_limit) {
var e = new Error("Skipped, rate limit exceeded");
e.name = "HTTP";
e.code = "RATELIMIT";
@ -54,7 +52,7 @@ exp.get_from_options = function(rid, url, options, callback) {
callback(null, response, e);
} else {
is_session_req && session_requests.push(Date.now());
session_req && session_requests.push(Date.now());
request.get({
url: url,
headers: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 428 B

View File

@ -1,64 +1,34 @@
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
var xhr = new XMLHttpRequest();
var quotes = [
["Crafatar is the best at what it does.", "Shotbow Network", "https://twitter.com/ShotbowNetwork/status/565201303555829762"],
["Crafatar seems to stand out from others", "Dabsunter", "https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar"],
["I cant tell you how much Crafatar helped me along the way! You guys do some amazing work.", "Luke Chatton", "https://github.com/lukechatton"],
["It's just awesome! Keep up the good work", "Dannyps", "https://forums.spongepowered.org/t/title-cant-be-empty/4964/22"],
["It's one of the few services that actually does HTTP header caching correctly", "confuser", "https://github.com/BanManagement/BanManager-WebUI/issues/16#issuecomment-73230674"],
["It's so beautiful. &lt;3", "FerusGrim", "https://twitter.com/FerusGrim/status/642824817683656704"],
["Love it! It's great!", "Reddit User", "https://reddit.com/comments/2nth0j/-/cmh5771"],
["Such a useful service!", "Tim Z, NameMC", "https://twitter.com/CoderTimZ/status/602682146793349120"],
["Thanks for providing us with such a reliable service :)", "BeanBlockz", "https://twitter.com/BeanBlockz/status/743927789422845952"],
["This is excellent for my website! Good work.", "cyanide43", "https://reddit.com/comments/2nth0j/-/cmgpq85"],
["This is really cool!", "AlexWebber", "https://forums.spongepowered.org/t/crafatar-a-new-minecraft-avatar-service/4964/19"],
["This really is looking amazing. Absolutely love it!", "Enter_", "https://forums.spongepowered.org/t/crafatar-a-new-minecraft-avatar-service/4964/21"],
["We couldn't believe how flawless your API is, Good job!", "SenceServers", "https://twitter.com/SenceServers/status/697132506626265089"],
["WOW, Crafatar is FAST", "Rileriscool", "https://twitter.com/rileriscool/status/562057234986065921"],
["You deserve way more popularity", "McSlushie", "https://github.com/crafatar/crafatar/wiki/Credit/a8f37373531b1d2c2cb3557ba809542a2ed81626"],
["You do excellent work on Crafatar and are awesome! A very polished, concise & clean project.", "DrCorporate", "https://reddit.com/comments/2r1ns6/-/cnbq5f1"]
];
// shuffle quotes
for (i = quotes.length -1; i > 0; i--) {
var a = Math.floor(Math.random() * i);
var b = quotes[i];
quotes[i] = quotes[a];
quotes[a] = b;
}
xhr.onload = function() {
var response = JSON.parse(xhr.responseText);
var status = {};
response.map(function(elem) {
var key = Object.keys(elem)[0];
status[key] = elem[key];
});
var current_quote = 0;
function changeQuote() {
var elem = document.querySelector("#quote");
var quote = quotes[current_quote];
elem.innerHTML = "<b>“" + quote[0] + "”</b><br>― <i>" + quote[1] + "</i>";
elem.href = quote[2];
current_quote = (current_quote + 1) % quotes.length;
}
fetch('https://mc-heads.net/json/mc_status').then(r => r.json()).then(data => {
var textures_err = data.report.skins.status !== "up";
var session_err = data.report.session.status !== "up";
var textures_err = status["textures.minecraft.net"] !== "green";
var session_err = status["sessionserver.mojang.com"] !== "green";
if (textures_err || session_err) {
var warn = document.createElement("div");
warn.setAttribute("class", "alert alert-warning");
warn.setAttribute("role", "alert");
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect requests at Crafatar. <small><a href=\"https://mc-heads.net/mcstatus\" target=\"_blank\">check status</a>";
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect requests at Crafatar. <small><a href=\"https://help.mojang.com\" target=\"_blank\">check status</a>";
document.querySelector("#alerts").appendChild(warn);
}
});
};
document.addEventListener("DOMContentLoaded", function(event) {
var avatars = document.querySelector("#avatar-wrapper");
// shuffle avatars
for (var i = 0; i < avatars.children.length; i++) {
// shake 'em on down!
// https://stackoverflow.com/a/11972692/2517068
avatars.appendChild(avatars.children[Math.random() * i | 0]);
}
setInterval(changeQuote, 5000);
changeQuote();
var tryit = document.querySelector("#tryit");
var tryname = document.querySelector("#tryname");
var images = document.querySelectorAll(".tryit");
@ -74,4 +44,7 @@ document.addEventListener("DOMContentLoaded", function(event) {
images[j].src = images[j].dataset.src.replace("$", value);
}
};
xhr.open("GET", "https://status.mojang.com/check", true);
xhr.send();
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 691 B

View File

@ -58,22 +58,6 @@ a.sponsor-item {
background: #fff8ec !important;
}
#quote-wrapper {
line-height: 9.5em;
}
#quote {
display: inline-block;
vertical-align: middle;
line-height: initial;
background: #d4e7ff;
border-color: #94cbfc;
}
#quote:hover {
background: #dcedff;
}
.alert {
font-size: 1rem;
}

View File

@ -95,13 +95,7 @@ module.exports = function(request, response, result) {
if (result.err && result.err.code === "ENOENT") {
result.code = result.code || 500;
}
if (!result.code) {
// Don't use 502 on Cloudflare
// As they will show their own error page instead
// https://support.cloudflare.com/hc/en-us/articles/200172706
result.code = config.caching.cloudflare ? 500 : 502;
}
response.writeHead(result.code, headers);
response.writeHead(result.code || 502, headers);
} else {
if (result.body) {
if (result.status === 4) {

View File

@ -14,10 +14,12 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
if (helpers.id_valid(def)) {
// clean up the old URL to match new image
req.url.searchParams.delete('default');
req.url.path_list[1] = def;
req.url.pathname = req.url.path_list.join('/');
var newUrl = req.url.toString();
var parsed = req.url;
delete parsed.query.default;
delete parsed.search;
parsed.path_list[1] = def;
parsed.pathname = "/" + parsed.path_list.join("/");
var newUrl = url.format(parsed);
callback({
status: img_status,
redirect: newUrl,
@ -51,9 +53,9 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
// GET avatar request
module.exports = function(req, callback) {
var userId = (req.url.path_list[1] || "").split(".")[0];
var size = parseInt(req.url.searchParams.get("size")) || config.avatars.default_size;
var def = req.url.searchParams.get("default");
var overlay = req.url.searchParams.has("overlay") || req.url.searchParams.has("helm");
var size = parseInt(req.url.query.size) || config.avatars.default_size;
var def = req.url.query.default;
var overlay = Object.prototype.hasOwnProperty.call(req.url.query, "overlay") || Object.prototype.hasOwnProperty.call(req.url.query, "helm");
// check for extra paths
if (req.url.path_list.length > 2) {
@ -65,9 +67,6 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
// Prevent app from crashing/freezing
if (size < config.avatars.min_size || size > config.avatars.max_size) {
// "Unprocessable Entity", valid request, but semantically erroneous:
@ -85,6 +84,9 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
try {
helpers.get_avatar(req.id, userId, overlay, size, function(err, status, image, hash) {
if (err) {

View File

@ -4,7 +4,7 @@ var cache = require("../cache");
// GET cape request
module.exports = function(req, callback) {
var userId = (req.url.path_list[1] || "").split(".")[0];
var def = req.url.searchParams.get('default');
var def = req.url.query.default;
var rid = req.id;
// check for extra paths
@ -17,8 +17,6 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
if (!helpers.id_valid(userId)) {
callback({
status: -2,
@ -27,6 +25,9 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
try {
helpers.get_cape(rid, userId, function(err, hash, status, image) {
if (err) {

View File

@ -7,7 +7,6 @@ var ejs = require("ejs");
var str;
var index;
// pre-compile the index page
function compile() {
logging.log("Compiling index page");
str = read(path.join(__dirname, "..", "views", "index.html.ejs"), "utf-8");

View File

@ -17,10 +17,12 @@ function handle_default(rid, scale, overlay, body, img_status, userId, size, def
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
if (helpers.id_valid(def)) {
// clean up the old URL to match new image
req.url.searchParams.delete('default');
req.url.path_list[2] = def;
req.url.pathname = req.url.path_list.join('/');
var newUrl = req.url.toString();
var parsed = req.url;
delete parsed.query.default;
delete parsed.search;
parsed.path_list[2] = def;
parsed.pathname = "/" + parsed.path_list.join("/");
var newUrl = url.format(parsed);
callback({
status: img_status,
redirect: newUrl,
@ -60,9 +62,9 @@ module.exports = function(req, callback) {
var rid = req.id;
var body = raw_type === "body";
var userId = (req.url.path_list[2] || "").split(".")[0];
var def = req.url.searchParams.get("default");
var scale = parseInt(req.url.searchParams.get("scale")) || config.renders.default_scale;
var overlay = req.url.searchParams.has("overlay") || req.url.searchParams.has("helm");
var def = req.url.query.default;
var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
var overlay = Object.prototype.hasOwnProperty.call(req.url.query, "overlay") || Object.prototype.hasOwnProperty.call(req.url.query, "helm");
// check for extra paths
if (req.url.path_list.length > 3) {
@ -83,9 +85,6 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
if (scale < config.renders.min_scale || scale > config.renders.max_scale) {
callback({
status: -2,
@ -100,6 +99,9 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
try {
helpers.get_render(rid, userId, scale, overlay, body, function(err, status, hash, image) {
if (err) {

View File

@ -14,10 +14,12 @@ function handle_default(img_status, userId, def, req, err, callback) {
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
if (helpers.id_valid(def)) {
// clean up the old URL to match new image
req.url.searchParams.delete('default');
req.url.path_list[1] = def;
req.url.pathname = req.url.path_list.join('/');
var newUrl = req.url.toString();
var parsed = req.url;
delete parsed.query.default;
delete parsed.search;
parsed.path_list[1] = def;
parsed.pathname = "/" + parsed.path_list.join("/");
var newUrl = url.format(parsed);
callback({
status: img_status,
redirect: newUrl,
@ -60,7 +62,7 @@ function handle_default(img_status, userId, def, req, err, callback) {
// GET skin request
module.exports = function(req, callback) {
var userId = (req.url.path_list[1] || "").split(".")[0];
var def = req.url.searchParams.get("default");
var def = req.url.query.default;
var rid = req.id;
// check for extra paths
@ -73,8 +75,6 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
if (!helpers.id_valid(userId)) {
callback({
status: -2,
@ -83,6 +83,9 @@ module.exports = function(req, callback) {
return;
}
// strip dashes
userId = userId.replace(/-/g, "");
try {
helpers.get_skin(rid, userId, function(err, hash, status, image, slim) {
if (err) {

View File

@ -1,7 +1,6 @@
#!/usr/bin/env node
var querystring = require("querystring");
var response = require("./response");
var helpers = require("./helpers.js");
var toobusy = require("toobusy-js");
var logging = require("./logging");
var config = require("../config");
@ -22,9 +21,7 @@ var routes = {
// serves assets from lib/public
function asset_request(req, callback) {
const filename = path.join(__dirname, "public", ...req.url.path_list);
const relative = path.relative(path.join(__dirname, "public"), filename);
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
var filename = path.join(__dirname, "public", req.url.path_list.join("/"));
fs.access(filename, function(fs_err) {
if (!fs_err) {
fs.readFile(filename, function(err, data) {
@ -42,13 +39,6 @@ function asset_request(req, callback) {
});
}
});
} else {
callback({
body: "Forbidden",
status: -2,
code: 403,
});
}
}
// generates a 12 character random string
@ -56,18 +46,26 @@ function request_id() {
return Math.random().toString(36).substring(2, 14);
}
// splits decoded URL path into an Array
// splits a URL path into an Array
// the path is resolved and decoded
function path_list(pathname) {
// remove double and trailing slashes
pathname = pathname.replace(/\/\/+/g, "/").replace(/(.)\/$/, "$1");
var list = pathname.split("/");
list.shift();
for (var i = 0; i < list.length; i++) {
// URL decode
list[i] = querystring.unescape(list[i]);
}
return list;
}
// handles the +req+ by routing to the request to the appropriate module
function requestHandler(req, res) {
req.url = new URL(decodeURI(req.url), 'http://' + req.headers.host);
req.url.pathname = path.resolve('/', req.url.pathname);
req.url = url.parse(req.url, true);
req.url.query = req.url.query || {};
req.url.path_list = path_list(req.url.pathname);
req.id = request_id();
req.start = Date.now();
@ -137,7 +135,6 @@ function requestHandler(req, res) {
var exp = {};
// Start the server
exp.boot = function(callback) {
var port = config.server.port;
var bind_ip = config.server.bind;
@ -166,9 +163,7 @@ exp.boot = function(callback) {
});
};
// Close the server
exp.close = function(callback) {
helpers.stoplog();
server.close(callback);
};

View File

@ -3,7 +3,7 @@
<head>
<title>Crafatar A blazing fast API for Minecraft faces!</title>
<meta charset="utf-8">
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="icon" sizes="16x16" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/stylesheets/bootstrap.min.css">
<link rel="stylesheet" href="/stylesheets/style.css">
<meta name="description" content="A blazing fast API for Minecraft faces with support for avatars, skins, and 3D renders!">
@ -76,7 +76,7 @@
</div>
</div>
</form>
<p>You can use <a rel="nofollow" target="_blank" href="https://minecraftuuid.com">minecraftuuid.com</a> to find the UUID of a username.</p>
<p>You can use <a rel="nofollow" target="_blank" href="https://mcuuid.net">mcuuid.net</a> to find the UUID of a username.</p>
</section>
<section id="avatars">
@ -227,12 +227,7 @@
<section id="meta-http-headers">
<h3><a href="#meta-http-headers">HTTP Headers</a></h3>
<p>
Crafatar always replies with a <code>200 OK</code> status code when the requested user's skin/cape was found. This is also used in some rare cases when Mojang servers are having issues and the image couldn't be checked for changes, but Crafatar still had a cached version.
<% if (config.caching.cloudflare) { %>
<code>500 Server Error</code> is used when no skin/cape was found because of Mojang or Crafatar server issues.
<% } else { %>
<code>502 Bad Gateway</code> and <code>500 Server Error</code> are used when no skin/cape was found because of Mojang or Crafatar server issues.
<% } %>
Crafatar always replies with a <code>200 OK</code> status code when the requested user's skin/cape was found. This is also used in some rare cases when Mojang servers are having issues and the image couldn't be checked for changes, but Crafatar still had a cached version. <code>502 Bad Gateway</code> and <code>500 Server Error</code> are used when no skin/cape was found because of Mojang or Crafatar server issues.
</p>
<p>
Note that requests are usually answered with an image (with Steve/Alex skin), even if an error occured!
@ -277,28 +272,23 @@
<div class="col-md-3">
<h4>Popular Crafatar users</h4>
<div class="list-group">
<a rel="nofollow" href="http://technicpack.net" target="_blank" class="list-group-item">Technic</a>
<a rel="nofollow" href="https://hypixel.net" target="_blank" class="list-group-item">Hypixel</a>
<a rel="nofollow" href="https://mineplex.com" target="_blank" class="list-group-item">Mineplex</a>
<a rel="nofollow" href="https://hivemc.com" target="_blank" class="list-group-item">The Hive</a>
<a rel="nofollow" href="https://www.technicpack.net" target="_blank" class="list-group-item">Technic Pack</a>
<a rel="nofollow" href="https://namemc.com" target="_blank" class="list-group-item">NameMC</a>
<a rel="nofollow" href="https://www.minecraft-index.com" target="_blank" class="list-group-item">MC Index</a>
<a rel="nofollow" href="https://www.minehq.com/hcteams/leaderboards" target="_blank" class="list-group-item">MineHQ</a>
<a rel="nofollow" href="https://shotbow.net" target="_blank" class="list-group-item">Shotbow</a>
<a rel="nofollow" href="https://mcuuid.net/" target="_blank" class="list-group-item">MCUUID</a>
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F" target="_blank" class="list-group-item">and many more…</a>
</div>
<hr>
<h4>Quotes</h4>
<div id="quote-wrapper" class="list-group">
<a id="quote" rel="nofollow" target="_blank" class="list-group-item"></a>
</div>
<p>See <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">all quotes</a>.</p>
<p>See also: <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">what users say</a> about Crafatar</p>
<hr>
<h4>Crafatar Tools & Plugins</h4>
<div class="list-group">
<a rel="nofollow" href="https://github.com/DiscordSRV/DiscordSRV#readme" target="_blank" class="list-group-item">DiscordSRV</a>
<a rel="nofollow" href="https://github.com/the-obsidian/discourse-minecraft-avatar" target="_blank" class="list-group-item">Discourse Minecraft Avatar</a>
<a rel="nofollow" href="https://xenforo.com/community/resources/associationmc.3232/" target="_blank" class="list-group-item">AssociationMc <i>(XenForo)</i></a>
<a rel="nofollow" href="https://open.vanillaforums.com/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Spigot/Bukkit)</i></a>
<a rel="nofollow" href="https://github.com/yeahwhat-mc/discourse-yeahwhat" target="_blank" class="list-group-item">Minecraft Heads <i>(Discourse)</i></a>
<a rel="nofollow" href="http://vanillaforums.org/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Bukkit)</i></a>
<a rel="nofollow" href="https://github.com/sk89q/Plumeria" target="_blank" class="list-group-item">Plumeria <i>(Discord)</i></a>
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F#other-services-using-crafatar" target="_blank" class="list-group-item">and many more…</a>
</div>
<hr>

2424
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,51 @@
{
"name": "crafatar",
"version": "2.1.5",
"version": "2.1.2",
"private": true,
"description": "A blazing fast API for Minecraft faces!",
"contributors": [
{
"name": "jomo",
"url": "https://github.com/jomo"
},
{
"name": "Jake",
"url": "https://github.com/Jake0oo0"
}
],
"repository": {
"type": "git",
"url": "https://github.com/crafatar/crafatar"
},
"issues": {
"url": "https://github.com/crafatar/crafatar/issues"
},
"keywords": [
"minecraft",
"avatar"
],
"scripts": {
"start": "node www.js",
"test": "mocha"
"test": "mocha",
"test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
"engines": {
"node": "12.16.1"
},
"dependencies": {
"@randy.tarampi/lwip": "^1.3.1",
"@randy.tarampi/lwip": "^1.1.0",
"canvas": "^2.6.1",
"crc": "^3.8.0",
"ejs": "^3.1.5",
"mime": "^2.4.6",
"ejs": "^3.0.1",
"mime": "^2.4.4",
"redis": "^3.0.2",
"request": "^2.88.2",
"toobusy-js": "^0.5.1"
},
"devDependencies": {
"mocha": "^7.2.0"
"coveralls": "^3.0.11",
"istanbul": "^0.4.5",
"mocha": "^7.1.1",
"mocha-lcov-reporter": "^1.3.0"
}
}

View File

@ -1,55 +1,36 @@
#!/usr/bin/env bash
hostname="crafatar.com"
async="true"
random="false"
interval="0.1"
usage() {
echo "Usage: $0 [-s | -r | -i <interval> | -h <hostname>]... <host uri>" >&2
exit 1
}
get_ids() {
local shuf
if [ "$random" = "true" ]; then
while true; do uuid -v 4; done
else
# `brew install coreutils` on OS X for gshuf
shuf=$(command -v shuf gshuf)
# randomize ids
$shuf < uuids.txt
if [ "$1" = "-s" ]; then
async=""
shift
elif [ "$1" = "-i" ]; then
interval="$2"
shift 2
fi
}
host="$1"
shift
if [ -z "$host" ] || [ ! -z "$@" ]; then
echo "Usage: $0 [-s | -i <interval>] <host uri>"
exit 1
fi
# insert newline after uuids
ids="$(cat 'uuids.txt')"
# `brew install coreutils` on OS X
ids="$(shuf <<< "$ids" 2>/dev/null || gshuf <<< "$ids")"
bulk() {
trap return INT # return from this function on Ctrl+C
get_ids | while read id; do
if [ "$async" = "false" ]; then
curl -H "Host: $hostname" -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
trap return INT
echo "$ids" | while read id; do
if [ -z "$async" ]; then
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
else
curl -H "Host: $hostname" -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
sleep "$interval"
fi
done
}
while [ $# != 0 ]; do
case "$1" in
-s)
async="false";;
-r)
random="true";;
-i)
interval="$2"
shift;;
*)
[ -n "$host" ] && usage
host="$1";;
esac
shift
done
[ -z "$host" ] && usage
time bulk

View File

@ -3,7 +3,7 @@
// no spam
var logging = require("../lib/logging");
if (process.env.VERBOSE_TEST !== "true") {
if (process.env.VERBOSE_TEST !== "true" && process.env.TRAVIS !== "true") {
logging.log = logging.debug = logging.warn = logging.error = function() {};
}
@ -88,8 +88,8 @@ describe("Crafatar", function() {
assert.strictEqual(helpers.id_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
done();
});
it("dashed uuid is not valid", function(done) {
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), false);
it("dashed uuid is valid", function(done) {
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), true);
done();
});
it("username is invalid", function(done) {
@ -158,7 +158,7 @@ describe("Crafatar", function() {
it("should time out on skin download", function(done) {
var original_timeout = config.http_timeout;
config.server.http_timeout = 1;
networking.get_from(rid(), config.endpoints.textures_url + "477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
networking.get_from(rid(), "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(error.code), -1);
config.server.http_timeout = original_timeout;
done();
@ -166,7 +166,7 @@ describe("Crafatar", function() {
});
it("should not find the skin", function(done) {
assert.doesNotThrow(function() {
networking.get_from(rid(), config.endpoints.textures_url + "this-does-not-exist", function(img, response, err) {
networking.get_from(rid(), "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
done();
});
@ -298,24 +298,20 @@ describe("Crafatar", function() {
var server_tests = {
"avatar with existing uuid": {
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
crc32: [4264176600],
},
"avatar with existing dashed uuid": {
url: "http://localhost:3000/avatars/853c80ef-3c37-49fd-aa49938b674adae6?size=16",
crc32: [4264176600],
crc32: [3337292777],
},
"avatar with non-existent uuid": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
crc32: [3348154329],
crc32: [2416827277, 1243826040],
},
"avatar with non-existent uuid defaulting to mhf_alex": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
crc32: [73899130],
crc32: [862751081, 809395677],
},
"avatar with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
crc32: [0],
redirect: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
},
"avatar with non-existent uuid defaulting to url": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -324,20 +320,20 @@ describe("Crafatar", function() {
},
"overlay avatar with existing uuid": {
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
crc32: [575355728],
crc32: [1710265722],
},
"overlay avatar with non-existent uuid": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
crc32: [3348154329],
crc32: [2416827277, 1243826040],
},
"overlay avatar with non-existent uuid defaulting to mhf_alex": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
crc32: [73899130],
crc32: [862751081, 809395677],
},
"overlay avatar with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
crc32: [0],
redirect: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
},
"overlay avatar with non-existent uuid defaulting to url": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -346,7 +342,7 @@ describe("Crafatar", function() {
},
"cape with existing uuid": {
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
crc32: [985789174, 2099310578],
crc32: [2556702429],
},
"cape with non-existent uuid": {
url: "http://localhost:3000/capes/00000000000000000000000000000000",
@ -359,20 +355,20 @@ describe("Crafatar", function() {
},
"skin with existing uuid": {
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
crc32: [1759176487],
crc32: [26500336],
},
"skin with non-existent uuid": {
url: "http://localhost:3000/skins/00000000000000000000000000000000",
crc32: [1853029228],
crc32: [981937087],
},
"skin with non-existent uuid defaulting to mhf_alex": {
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
crc32: [427506205],
crc32: [2298915739],
},
"skin with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
crc32: [0],
redirect: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
},
"skin with non-existent uuid defaulting to url": {
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -394,7 +390,7 @@ describe("Crafatar", function() {
"head render with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
crc32: [0],
redirect: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
},
"head render with non-existent uuid defaulting to url": {
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -416,7 +412,7 @@ describe("Crafatar", function() {
"overlay head with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
crc32: [0],
redirect: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
},
"overlay head render with non-existent uuid defaulting to url": {
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -425,7 +421,7 @@ describe("Crafatar", function() {
},
"body render with existing uuid": {
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
crc32: [1144887125],
crc32: [2745192436],
},
"body render with non-existent uuid": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
@ -433,12 +429,12 @@ describe("Crafatar", function() {
},
"body render with non-existent uuid defaulting to mhf_alex": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
crc32: [4280894468],
crc32: [1255106465],
},
"body render with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
crc32: [0],
redirect: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
},
"body render with non-existent uuid defaulting to url": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -447,7 +443,7 @@ describe("Crafatar", function() {
},
"overlay body render with existing uuid": {
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
crc32: [1107696668],
crc32: [2441671793],
},
"overlay body render with non-existent uuid": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
@ -455,7 +451,7 @@ describe("Crafatar", function() {
},
"overlay body render with non-existent uuid defaulting to mhf_alex": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
crc32: [4280894468],
crc32: [1255106465],
},
"overlay body render with non-existent uuid defaulting to url": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
@ -539,7 +535,7 @@ describe("Crafatar", function() {
});
it("should return a 422 (invalid render type)", function(done) {
request.get("http://localhost:3000/renders/invalid/2d5aa9cdaeb049189930461fc9b91cc5", function(error, res, body) {
request.get("http://localhost:3000/renders/invalid/Jake_0", function(error, res, body) {
assert.ifError(error);
assert.strictEqual(res.statusCode, 422);
done();
@ -568,30 +564,6 @@ describe("Crafatar", function() {
});
}(loc));
}
it("should return /public resources", function(done) {
request.get("http://localhost:3000/javascript/crafatar.js", function(error, res, body) {
assert.ifError(error);
assert.strictEqual(res.statusCode, 200);
done();
});
});
it("should not allow path traversal on /public", function(done) {
request.get("http://localhost:3000/../server.js", function(error, res, body) {
assert.ifError(error);
assert.strictEqual(res.statusCode, 404);
done();
});
});
it("should not allow encoded path traversal on /public", function(done) {
request.get("http://localhost:3000/%2E%2E/server.js", function(error, res, body) {
assert.ifError(error);
assert.strictEqual(res.statusCode, 404);
done();
});
});
});
// we have to make sure that we test both a 32x64 and 64x64 skin
@ -612,7 +584,7 @@ describe("Crafatar", function() {
describe("Networking: Cape", function() {
it("should not fail (guaranteed cape)", function(done) {
helpers.get_cape(rid(), "61699b2ed3274a019f1e0ea8c3f06bc6", function(err, hash, status, img) {
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@ -621,13 +593,13 @@ describe("Crafatar", function() {
before(function() {
cache.get_redis().flushall();
});
helpers.get_cape(rid(), "61699b2ed3274a019f1e0ea8c3f06bc6", function(err, hash, status, img) {
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
});
it("should not be found", function(done) {
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
assert.ifError(err);
assert.strictEqual(img, null);
done();
@ -637,7 +609,7 @@ describe("Crafatar", function() {
describe("Networking: Skin", function() {
it("should not fail", function(done) {
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@ -646,7 +618,7 @@ describe("Crafatar", function() {
before(function() {
cache.get_redis().flushall();
});
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@ -714,16 +686,15 @@ describe("Crafatar", function() {
cache.get_redis().flushall();
});
// Mojang has changed its rate limiting, so we no longer expect to hit the rate limit
// it("uuid SHOULD be rate limited", function(done) {
// networking.get_profile(rid(), uuid, function() {
// networking.get_profile(rid(), uuid, function(err, profile) {
// assert.strictEqual(err.toString(), "HTTP: 429");
// assert.strictEqual(profile, null);
// done();
// });
// });
// });
it("uuid SHOULD be rate limited", function(done) {
networking.get_profile(rid(), uuid, function() {
networking.get_profile(rid(), uuid, function(err, profile) {
assert.strictEqual(err.toString(), "HTTP: 429");
assert.strictEqual(profile, null);
done();
});
});
});
it("CloudFront rate limit is handled", function(done) {
var original_rate_limit = config.server.sessions_rate_limit;