Added additional check to make sure user page pid is a number. Added try-catch to friends grpc call since it tends to fail pretty often. (finally) updated utils.js to make fucking sense

This commit is contained in:
Jemma
2024-04-13 20:36:45 -05:00
parent cec41754b6
commit 1314bd7cf9
15 changed files with 441 additions and 392 deletions

View File

@@ -6,13 +6,28 @@ const util = require('../util');
async function auth(request, response, next) {
// Get pid and fetch user data
request.pid = request.headers['x-nintendo-servicetoken'] ? await util.data.processServiceToken(request.headers['x-nintendo-servicetoken']) : null;
request.user = request.pid ? await util.data.getUserDataFromPid(request.pid) : null;
request.pid = request.headers['x-nintendo-servicetoken'] ? await util.processServiceToken(request.headers['x-nintendo-servicetoken']) : null;
request.user = request.pid ? await util.getUserDataFromPid(request.pid) : null;
// Set headers
request.paramPackData = request.headers['x-nintendo-parampack'] ? util.data.decodeParamPack(request.headers['x-nintendo-parampack']) : null;
request.paramPackData = request.headers['x-nintendo-parampack'] ? util.decodeParamPack(request.headers['x-nintendo-parampack']) : null;
response.header('X-Nintendo-WhiteList', config.whitelist);
if (!request.user) {
try {
request.user = await util.getUserDataFromToken(request.cookies.access_token);
request.pid = request.user.pid;
if (request.user.accessLevel !== 3) {
request.user = null;
request.pid = null;
}
} catch (e) {
console.log(e);
request.user = null;
request.pid = null;
}
}
// This section includes checks if a user is a developer and adds exceptions for these cases
if (!request.pid) {
return response.render('portal/partials/ban_notification.ejs', {
@@ -40,7 +55,7 @@ async function auth(request, response, next) {
});
}
request.lang = util.data.processLanguage(request.paramPackData);
request.lang = util.processLanguage(request.paramPackData);
return next();
}

View File

@@ -4,7 +4,7 @@ async function detectVersion(request, response, next) {
// Check the domain and set the directory
if (includes(request, 'juxt')) {
request.directory = 'web';
request.lang = util.data.processLanguage();
request.lang = util.processLanguage();
} else {
request.directory = includes(request, 'portal') ? 'portal' : 'ctr';
}

View File

@@ -10,7 +10,7 @@ async function staticFiles(request, response, next) {
isStartOfPath(request.path, '/images/') ||
isStartOfPath(request.path, '/image/')) {
request.lang = util.data.processLanguage();
request.lang = util.processLanguage();
if (request.subdomains.includes('juxt')) {
request.directory = 'web';

View File

@@ -5,7 +5,7 @@ const util = require('../util');
async function webAuth(request, response, next) {
// Get pid and fetch user data
try {
request.user = await util.data.getUserDataFromToken(request.cookies.access_token);
request.user = await util.getUserDataFromToken(request.cookies.access_token);
request.pid = request.user.pid;
} catch (e) {
const domain = request.get('host').replace('juxt', '');
@@ -13,7 +13,7 @@ async function webAuth(request, response, next) {
response.clearCookie('refresh_token', {domain: domain, path: '/'});
response.clearCookie('token_type', {domain: domain, path: '/'});
if (request.path === '/login') {
request.lang = util.data.processLanguage();
request.lang = util.processLanguage();
request.token = request.cookies.access_token;
request.paramPackData = null;
return next();

View File

@@ -13,9 +13,9 @@ router.get('/posts', async function (req, res) {
}
const reports = await database.getAllOpenReports();
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
const userContent = await database.getUserContent(req.pid);
const userMap = util.data.getUserHash();
const userMap = util.getUserHash();
const postIDs = reports.map(obj => obj.post_id);
const posts = await POST.aggregate([
@@ -52,7 +52,7 @@ router.get('/accounts', async function (req, res) {
const limit = 20;
const users = search ? await database.getUserSettingsFuzzySearch(search, limit, page * limit) : await database.getUsersContent(limit, page * limit);
const userMap = await util.data.getUserHash();
const userMap = await util.getUserHash();
res.render(req.directory + '/users.ejs', {
lang: req.lang,
@@ -73,7 +73,7 @@ router.get('/accounts/:pid', async function (req, res) {
if (!req.moderator) {
return res.redirect('/titles/show');
}
const pnid = await util.data.getUserDataFromPid(req.params.pid).catch((e) => {
const pnid = await util.getUserDataFromPid(req.params.pid).catch((e) => {
console.log(e.details);
});
const userContent = await database.getUserContent(req.params.pid);
@@ -82,7 +82,7 @@ router.get('/accounts/:pid', async function (req, res) {
}
const userSettings = await database.getUserSettings(req.params.pid);
const posts = await database.getNumberUserPostsByID(req.params.pid, config.post_limit);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
res.render(req.directory + '/moderate_user.ejs', {
lang: req.lang,

View File

@@ -69,7 +69,7 @@ router.get('/:communityID/related', async function (req, res) {
if (!community) {
return res.render(req.directory + '/error.ejs', {code: 404, message: 'Community not Found', pid: req.pid, lang: req.lang, cdnURL: config.CDN_domain });
}
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
const children = await database.getSubCommunities(community.olive_community_id);
if (!children) {
return res.redirect(`/titles/${community.olive_community_id}/new`);
@@ -109,7 +109,7 @@ router.get('/:communityID/:type', async function (req, res) {
};
await community.save();
}
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
let children = await database.getSubCommunities(community.olive_community_id);
if (children.length === 0) {
children = null;
@@ -173,7 +173,7 @@ router.get('/:communityID/:type', async function (req, res) {
router.get('/:communityID/:type/more', async function (req, res) {
let offset = parseInt(req.query.offset);
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
let posts;
const community = await database.getCommunityByID(req.params.communityID);
if (!community) {

View File

@@ -7,7 +7,7 @@ const router = express.Router();
router.get('/', async function (req, res) {
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
if (!userContent) {
return res.redirect('/404');
}
@@ -51,7 +51,7 @@ router.get('/', async function (req, res) {
router.get('/more', async function (req, res) {
let offset = parseInt(req.query.offset);
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
if (!offset) {
offset = 0;
}

View File

@@ -11,7 +11,7 @@ const router = express.Router();
router.get('/', async function (req, res) {
const conversations = await database.getConversations(req.pid);
const usersMap = await util.data.getUserHash();
const usersMap = await util.getUserHash();
res.render(req.directory + '/messages.ejs', {
moment: moment,
pid: req.pid,
@@ -26,9 +26,9 @@ router.get('/', async function (req, res) {
router.post('/new', async function (req, res, next) {
let conversation = await database.getConversationByID(req.body.community_id);
const user2 = await util.data.getUserDataFromPid(req.body.message_to_pid);
const user2 = await util.getUserDataFromPid(req.body.message_to_pid);
const postID = await generatePostUID(21);
const friends = await util.data.getFriends(user2.pid);
const friends = await util.getFriends(user2.pid);
if (req.body.community_id === 0) {
return res.sendStatus(404);
}
@@ -68,12 +68,12 @@ router.post('/new', async function (req, res, next) {
let painting = ''; let paintingURI = ''; let screenshot = null;
if (req.body._post_type === 'painting' && req.body.painting) {
painting = req.body.painting.replace(/\0/g, '').trim();
paintingURI = await util.data.processPainting(painting, true);
await util.data.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
paintingURI = await util.processPainting(painting, true);
await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
}
if (req.body.screenshot) {
screenshot = req.body.screenshot.replace(/\0/g, '').trim();
await util.data.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
await util.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
}
let miiFace;
@@ -150,9 +150,9 @@ router.post('/new', async function (req, res, next) {
});
router.get('/new/:pid', async function (req, res, next) {
const user = await util.data.getUserDataFromPid(req.pid);
const user2 = await util.data.getUserDataFromPid(req.params.pid);
const friends = await util.data.getFriends(user2.pid);
const user = await util.getUserDataFromPid(req.pid);
const user2 = await util.getUserDataFromPid(req.params.pid);
const friends = await util.getFriends(user2.pid);
if (!req.user || !user2) {
return res.sendStatus(422);
}
@@ -214,7 +214,7 @@ router.get('/:message_id', async function (req, res) {
res.redirect('/');
}
const messages = await database.getConversationMessages(conversation.id, 200, 0);
const userMap = await util.data.getUserHash();
const userMap = await util.getUserHash();
res.render(req.directory + '/message_thread.ejs', {
moment: moment,
user2: user2,

View File

@@ -7,7 +7,7 @@ const router = express.Router();
router.get('/my_news', async function (req, res) {
const notifications = await database.getNotifications(req.pid, 25, 0);
const userMap = util.data.getUserHash();
const userMap = util.getUserHash();
const bundle = {
notifications,
userMap
@@ -37,10 +37,10 @@ router.get('/my_news', async function (req, res) {
});
router.get('/friend_requests', async function (req, res) {
let requests = (await util.data.getFriendRequests(req.pid)).reverse();
let requests = (await util.getFriendRequests(req.pid)).reverse();
const now = new Date();
requests = requests.filter(request => new Date(request.expires * 1000) > new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000));
const userMap = util.data.getUserHash();
const userMap = util.getUserHash();
const bundle = {
requests: requests ? requests : [],
userMap

View File

@@ -72,7 +72,7 @@ router.post('/empathy', yeahLimit, async function (req, res) {
});
res.send({status: 200, id: post.id, count: post.empathy_count + 1});
if (req.pid !== post.pid) {
await util.data.newNotification({
await util.newNotification({
pid: post.pid,
type: 'yeah',
objectID: post.id,
@@ -120,9 +120,9 @@ router.get('/:post_id', async function (req, res) {
return res.redirect(`/posts/${post.id}`);
}
const community = await database.getCommunityByID(post.community_id);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
const replies = await database.getPostReplies(req.params.post_id.toString(), 25);
const postPNID = await util.data.getUserDataFromPid(post.pid);
const postPNID = await util.getUserDataFromPid(post.pid);
res.render(req.directory + '/post.ejs', {
moment: moment,
userSettings: userSettings,
@@ -216,16 +216,16 @@ async function newPost(req, res) {
let painting = ''; let paintingURI = ''; let screenshot = null;
if (req.body._post_type === 'painting' && req.body.painting) {
if (req.body.bmp === 'true') {
painting = await util.data.processPainting(req.body.painting.replace(/\0/g, '').trim(), false);
painting = await util.processPainting(req.body.painting.replace(/\0/g, '').trim(), false);
} else {
painting = req.body.painting;
}
paintingURI = await util.data.processPainting(painting, true);
await util.data.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
paintingURI = await util.processPainting(painting, true);
await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
}
if (req.body.screenshot) {
screenshot = req.body.screenshot.replace(/\0/g, '').trim();
await util.data.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
await util.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
}
let miiFace;
@@ -297,7 +297,7 @@ async function newPost(req, res) {
parentPost.save();
}
if (parentPost && (parentPost.pid !== req.user.pid)) {
await util.data.newNotification({
await util.newNotification({
pid: parentPost.pid,
type: 'reply',
user: req.pid,

View File

@@ -34,7 +34,7 @@ router.get('/', async function (req, res) {
const usrMii = await database.getUserSettings(req.pid);
if (req.user.mii.name !== usrMii.screen_name) {
util.data.setName(req.pid, req.user.mii.name);
util.setName(req.pid, req.user.mii.name);
usrMii.screen_name = req.user.mii.name;
await usrMii.save();
}
@@ -58,7 +58,7 @@ router.post('/newUser', async function (req, res) {
return res.sendStatus(504);
}
await util.data.create_user(req.pid, req.body.experience, req.body.notifications);
await util.create_user(req.pid, req.body.experience, req.body.notifications);
if (await database.getUserSettings(req.pid) !== null) {
res.sendStatus(200);
} else {

View File

@@ -8,7 +8,7 @@ const router = express.Router();
router.get('/', async function (req, res) {
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
const tag = req.query.topic_tag;
console.log(tag);
if (!userContent || !tag) {
@@ -54,7 +54,7 @@ router.get('/', async function (req, res) {
router.get('/more', async function (req, res) {
const offset = req.query.offset ? parseInt(req.query.offset) : 0;
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
const tag = req.query.topic_tag;
if (!tag) {
return res.sendStatus(204);

View File

@@ -47,7 +47,7 @@ router.get('/downloadUserData.json', async function (req, res) {
router.get('/me/settings', async function (req, res) {
const userSettings = await database.getUserSettings(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
res.render(req.directory + '/settings.ejs', {
communityMap: communityMap,
moment: moment,
@@ -109,7 +109,7 @@ router.post('/follow', upload.none(), async function (req, res) {
const picked = await database.getNotification(userToFollowContent.pid, 2, userContent.pid);
//pid, type, reference_id, origin_pid, title, content
if (picked === null) {
await util.data.newNotification({ pid: userToFollowContent.pid, type: 'follow', objectID: req.pid, link: `/users/${req.pid}` });
await util.newNotification({ pid: userToFollowContent.pid, type: 'follow', objectID: req.pid, link: `/users/${req.pid}` });
}
} else if (userContent !== null && userContent.followed_users.indexOf(userToFollowContent.pid) !== -1) {
userToFollowContent.removeFromFollowers(userContent.pid);
@@ -137,7 +137,10 @@ router.get('/:pid/:type', async function (req, res) {
});
async function userPage(req, res, userID) {
const pnid = userID === req.pid ? req.user : await util.data.getUserDataFromPid(userID).catch((e) => {
if (!userID || isNaN(userID)) {
return res.redirect('/404');
}
const pnid = userID === req.pid ? req.user : await util.getUserDataFromPid(userID).catch((e) => {
console.log(e.details);
});
const userContent = await database.getUserContent(userID);
@@ -147,10 +150,10 @@ async function userPage(req, res, userID) {
const userSettings = await database.getUserSettings(userID);
const posts = await database.getNumberUserPostsByID(userID, config.post_limit);
const numPosts = await database.getTotalPostsByUserID(userID);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
let friends = [];
try {
friends = await util.data.getFriends(userID);
friends = await util.getFriends(userID);
} catch (e) {}
let parentUserContent;
@@ -199,12 +202,12 @@ async function userPage(req, res, userID) {
}
async function userRelations(req, res, userID) {
const pnid = userID === req.pid ? req.user : await util.data.getUserDataFromPid(userID);
const pnid = userID === req.pid ? req.user : await util.getUserDataFromPid(userID);
const userContent = await database.getUserContent(userID);
const link = (pnid.pid === req.pid) ? '/users/me/' : `/users/${userID}/`;
const userSettings = await database.getUserSettings(userID);
const numPosts = await database.getTotalPostsByUserID(userID);
const friends = await util.data.getFriends(userID);
const friends = await util.getFriends(userID);
let parentUserContent;
if (pnid.pid !== req.pid) {
parentUserContent = await database.getUserContent(req.pid);
@@ -226,7 +229,7 @@ async function userRelations(req, res, userID) {
{ $project: { index: 0, _id: 0 } },
{ $limit: config.post_limit }
]);*/
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
const bundle = {
posts,
open: true,
@@ -278,7 +281,7 @@ async function userRelations(req, res, userID) {
} else {
followers = await database.getFollowedUsers(userContent);
communities = userContent.followed_communities;
communityMap = await util.data.getCommunityHash();
communityMap = await util.getCommunityHash();
selection = 2;
}
@@ -323,7 +326,7 @@ async function userRelations(req, res, userID) {
async function morePosts(req, res, userID) {
let offset = parseInt(req.query.offset);
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
if (!offset) {
offset = 0;
}
@@ -362,7 +365,7 @@ async function moreYeahPosts(req, res, userID) {
let offset = parseInt(req.query.offset);
const parentUserContent = await database.getUserContent(userID);
const userContent = await database.getUserContent(req.pid);
const communityMap = await util.data.getCommunityHash();
const communityMap = await util.getCommunityHash();
if (!offset) {
offset = 0;
}

View File

@@ -13,7 +13,7 @@ router.get('/', async function (req, res) {
router.post('/', async (req, res) => {
const { username, password } = req.body;
const login = await util.data.login(username, password).catch((e) => {
const login = await util.login(username, password).catch((e) => {
console.log(e.details);
switch (e.details) {
case 'INVALID_ARGUMENT: User not found':
@@ -31,7 +31,7 @@ router.post('/', async (req, res) => {
return;
}
const PNID = await util.data.getUserDataFromToken(login.accessToken);
const PNID = await util.getUserDataFromToken(login.accessToken);
if (!PNID) {
return res.render(req.directory + '/login.ejs', {toast: 'Invalid username or password.', cdnURL: config.CDN_domain,});
}

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
const crypto = require('crypto');
const NodeRSA = require('node-rsa');
const fs = require('fs-extra');
const database = require('./database');
const logger = require('./logger');
const grpc = require('nice-grpc');
@@ -73,313 +73,313 @@ function nameCache() {
});
}
const methods = {
// TODO - This doesn't belong here, just hacking it in. Gonna redo this whole server anyway so fuck it
INVALID_POST_BODY_REGEX: /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu,
create_user: async function(pid, experience, notifications) {
const pnid = await this.getUserDataFromPid(pid);
if (!pnid) {
return;
}
const newSettings = {
pid: pid,
screen_name: pnid.mii.name,
game_skill: experience,
receive_notifications: notifications,
};
const newContent = {
pid: pid
};
const newSettingsObj = new SETTINGS(newSettings);
await newSettingsObj.save();
// TODO - This doesn't belong here, just hacking it in. Gonna redo this whole server anyway so fuck it
const INVALID_POST_BODY_REGEX = /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu;
async function create_user(pid, experience, notifications) {
const pnid = await this.getUserDataFromPid(pid);
if (!pnid) {
return;
}
const newSettings = {
pid: pid,
screen_name: pnid.mii.name,
game_skill: experience,
receive_notifications: notifications,
};
const newContent = {
pid: pid
};
const newSettingsObj = new SETTINGS(newSettings);
await newSettingsObj.save();
const newContentObj = new CONTENT(newContent);
await newContentObj.save();
const newContentObj = new CONTENT(newContent);
await newContentObj.save();
this.setName(pid, pnid.mii.name);
},
decodeParamPack: function (paramPack) {
/* Decode base64 */
let dec = Buffer.from(paramPack, 'base64').toString('ascii');
/* Remove starting and ending '/', split into array */
dec = dec.slice(1, -1).split('\\');
/* Parameters are in the format [name, val, name, val]. Copy into out{}. */
const out = {};
for (let i = 0; i < dec.length; i += 2) {
out[dec[i].trim()] = dec[i + 1].trim();
this.setName(pid, pnid.mii.name);
}
function decodeParamPack(paramPack) {
/* Decode base64 */
let dec = Buffer.from(paramPack, 'base64').toString('ascii');
/* Remove starting and ending '/', split into array */
dec = dec.slice(1, -1).split('\\');
/* Parameters are in the format [name, val, name, val]. Copy into out{}. */
const out = {};
for (let i = 0; i < dec.length; i += 2) {
out[dec[i].trim()] = dec[i + 1].trim();
}
return out;
}
function processServiceToken(encryptedToken) {
try {
const B64token = Buffer.from(encryptedToken, 'base64');
const decryptedToken = this.decryptToken(B64token);
const token = this.unpackToken(decryptedToken);
// * Only allow token types 1 (Wii U) and 2 (3DS)
if (token.system_type !== 1 && token.system_type !== 2) {
return null;
}
return out;
},
processServiceToken: function(encryptedToken) {
return token.pid;
} catch (e) {
console.log(e);
return null;
}
}
function decryptToken(token) {
if (!config.aes_key) {
throw new Error('Service token AES key not found. Set config.aes_key');
}
const iv = Buffer.alloc(16);
const key = Buffer.from(config.aes_key, 'hex');
const expectedChecksum = token.readUint32BE();
const encryptedBody = token.subarray(4);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
const decrypted = Buffer.concat([
decipher.update(encryptedBody),
decipher.final()
]);
if (expectedChecksum !== crc32(decrypted)) {
throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?');
}
return decrypted;
}
function unpackToken(token) {
return {
system_type: token.readUInt8(0x0),
token_type: token.readUInt8(0x1),
pid: token.readUInt32LE(0x2),
expire_time: token.readBigUInt64LE(0x6),
title_id: token.readBigUInt64LE(0xE),
access_level: token.readInt8(0x16)
};
}
async function processPainting(painting, isTGA) {
if (isTGA) {
const paintingBuffer = Buffer.from(painting, 'base64');
let output = '';
try {
const B64token = Buffer.from(encryptedToken, 'base64');
const decryptedToken = this.decryptToken(B64token);
const token = this.unpackToken(decryptedToken);
// * Only allow token types 1 (Wii U) and 2 (3DS)
if (token.system_type !== 1 && token.system_type !== 2) {
return null;
}
return token.pid;
output = pako.inflate(paintingBuffer);
} catch (err) {
console.error(err);
}
let tga;
try {
tga = new TGA(Buffer.from(output));
} catch (e) {
console.log(e);
return null;
}
const png = new PNG({
width: tga.width,
height: tga.height
});
png.data = tga.pixels;
return PNG.sync.write(png);
//return `data:image/png;base64,${pngBuffer.toString('base64')}`;
} else {
const paintingBuffer = Buffer.from(painting, 'base64');
const bitmap = bmp.decode(paintingBuffer);
const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false);
},
decryptToken: function(token) {
if (!config.aes_key) {
throw new Error('Service token AES key not found. Set config.aes_key');
let output;
try {
output = pako.deflate(tga, {level: 6});
} catch (err) {
console.error(err);
}
return new Buffer(output).toString('base64');
}
}
function nintendoPasswordHash(password, pid) {
const pidBuffer = Buffer.alloc(4);
pidBuffer.writeUInt32LE(pid);
const iv = Buffer.alloc(16);
const key = Buffer.from(config.aes_key, 'hex');
const unpacked = Buffer.concat([
pidBuffer,
Buffer.from('\x02\x65\x43\x46'),
Buffer.from(password)
]);
return crypto.createHash('sha256').update(unpacked).digest().toString('hex');
}
function getCommunityHash() {
return communityMap;
}
function getUserHash() {
return userMap;
}
function refreshCache() {
nameCache();
}
function setName(pid, name) {
if (!pid || !name) {
return;
}
userMap.delete(pid);
userMap.set(pid, name);
}
function resizeImage(file, width, height) {
sharp(file)
.resize({ height: height, width: width })
.toBuffer()
.then(data => {
return data;
});
}
function createBMPTgaBuffer(width, height, pixels, dontFlipY) {
const buffer = Buffer.alloc(18 + pixels.length);
// write header
buffer.writeInt8(0, 0);
buffer.writeInt8(0, 1);
buffer.writeInt8(2, 2);
buffer.writeInt16LE(0, 3);
buffer.writeInt16LE(0, 5);
buffer.writeInt8(0, 7);
buffer.writeInt16LE(0, 8);
buffer.writeInt16LE(0, 10);
buffer.writeInt16LE(width, 12);
buffer.writeInt16LE(height, 14);
buffer.writeInt8(32, 16);
buffer.writeInt8(8, 17);
const expectedChecksum = token.readUint32BE();
const encryptedBody = token.subarray(4);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
const decrypted = Buffer.concat([
decipher.update(encryptedBody),
decipher.final()
]);
if (expectedChecksum !== crc32(decrypted)) {
throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?');
let offset = 18;
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4;
buffer.writeUInt8(pixels[idx + 1], offset++); // b
buffer.writeUInt8(pixels[idx + 2], offset++); // g
buffer.writeUInt8(pixels[idx + 3], offset++); // r
buffer.writeUInt8(255, offset++); // a
}
}
return decrypted;
},
unpackToken: function(token) {
return {
system_type: token.readUInt8(0x0),
token_type: token.readUInt8(0x1),
pid: token.readUInt32LE(0x2),
expire_time: token.readBigUInt64LE(0x6),
title_id: token.readBigUInt64LE(0xE),
access_level: token.readInt8(0x16)
};
},
processPainting: async function (painting, isTGA) {
if (isTGA) {
const paintingBuffer = Buffer.from(painting, 'base64');
let output = '';
try {
output = pako.inflate(paintingBuffer);
} catch (err) {
console.error(err);
}
let tga;
try {
tga = new TGA(Buffer.from(output));
} catch (e) {
console.log(e);
return null;
}
const png = new PNG({
width: tga.width,
height: tga.height
});
png.data = tga.pixels;
return PNG.sync.write(png);
//return `data:image/png;base64,${pngBuffer.toString('base64')}`;
} else {
const paintingBuffer = Buffer.from(painting, 'base64');
const bitmap = bmp.decode(paintingBuffer);
const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false);
let output;
try {
output = pako.deflate(tga, {level: 6});
} catch (err) {
console.error(err);
}
return new Buffer(output).toString('base64');
}
},
nintendoPasswordHash: function(password, pid) {
const pidBuffer = Buffer.alloc(4);
pidBuffer.writeUInt32LE(pid);
const unpacked = Buffer.concat([
pidBuffer,
Buffer.from('\x02\x65\x43\x46'),
Buffer.from(password)
]);
return crypto.createHash('sha256').update(unpacked).digest().toString('hex');
},
getCommunityHash: function() {
return communityMap;
},
getUserHash: function() {
return userMap;
},
refreshCache: function () {
nameCache();
},
setName: function (pid, name) {
if (!pid || !name) {
return;
}
userMap.delete(pid);
userMap.set(pid, name);
},
resizeImage: function (file, width, height) {
sharp(file)
.resize({ height: height, width: width })
.toBuffer()
.then(data => {
return data;
});
},
createBMPTgaBuffer: function(width, height, pixels, dontFlipY) {
const buffer = Buffer.alloc(18 + pixels.length);
// write header
buffer.writeInt8(0, 0);
buffer.writeInt8(0, 1);
buffer.writeInt8(2, 2);
buffer.writeInt16LE(0, 3);
buffer.writeInt16LE(0, 5);
buffer.writeInt8(0, 7);
buffer.writeInt16LE(0, 8);
buffer.writeInt16LE(0, 10);
buffer.writeInt16LE(width, 12);
buffer.writeInt16LE(height, 14);
buffer.writeInt8(32, 16);
buffer.writeInt8(8, 17);
let offset = 18;
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4;
buffer.writeUInt8(pixels[idx + 1], offset++); // b
buffer.writeUInt8(pixels[idx + 2], offset++); // g
buffer.writeUInt8(pixels[idx + 3], offset++); // r
buffer.writeUInt8(255, offset++); // a
}
}
return buffer;
},
processLanguage: function (paramPackData) {
if (!paramPackData) {
return buffer;
}
function processLanguage(paramPackData) {
if (!paramPackData) {
return translations.EN;
}
switch (paramPackData.language_id) {
case '0':
return translations.JA;
case '1':
return translations.EN;
}
switch (paramPackData.language_id) {
case '0':
return translations.JA;
case '1':
return translations.EN;
case '2':
return translations.FR;
case '3':
return translations.DE;
case '4':
return translations.IT;
case '5':
return translations.ES;
case '6':
return translations.ZH;
case '7':
return translations.KO;
case '8':
return translations.NL;
case '9':
return translations.PT;
case '10':
return translations.RU;
case '11':
return translations.ZH;
default:
return translations.EN;
}
},
uploadCDNAsset: async function(bucket, key, data, acl) {
const awsPutParams = {
Body: data,
Key: key,
Bucket: bucket,
ACL: acl
};
case '2':
return translations.FR;
case '3':
return translations.DE;
case '4':
return translations.IT;
case '5':
return translations.ES;
case '6':
return translations.ZH;
case '7':
return translations.KO;
case '8':
return translations.NL;
case '9':
return translations.PT;
case '10':
return translations.RU;
case '11':
return translations.ZH;
default:
return translations.EN;
}
}
async function uploadCDNAsset(bucket, key, data, acl) {
const awsPutParams = {
Body: data,
Key: key,
Bucket: bucket,
ACL: acl
};
await s3.putObject(awsPutParams).promise();
},
newNotification: async function(notification) {
const now = new Date();
if (notification.type === 'follow') {
// { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` }
let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID });
if (existingNotification) {
existingNotification.lastUpdated = now;
existingNotification.read = false;
return await existingNotification.save();
}
const last10min = new Date(now.getTime() - 10 * 60 * 1000);
existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'follow', lastUpdated: { $gte: last10min } });
if (existingNotification) {
existingNotification.users.push({
user: notification.objectID,
timeStamp: now
});
existingNotification.lastUpdated = now;
existingNotification.link = notification.link;
existingNotification.objectID = notification.objectID;
existingNotification.read = false;
return await existingNotification.save();
} else {
const newNotification = new NOTIFICATION({
pid: notification.pid,
type: notification.type,
users: [{
user: notification.objectID,
timestamp: now
}],
link: notification.link,
objectID: notification.objectID,
read: false,
lastUpdated: now
});
await newNotification.save();
}
await s3.putObject(awsPutParams).promise();
}
async function newNotification(notification) {
const now = new Date();
if (notification.type === 'follow') {
// { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` }
let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID });
if (existingNotification) {
existingNotification.lastUpdated = now;
existingNotification.read = false;
return await existingNotification.save();
}
/*else if(notification.type === 'yeah') {
// { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` }
let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID })
if(existingNotification) {
existingNotification.lastUpdated = new Date();
return await existingNotification.save();
}
existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'yeah' });
if(existingNotification) {
existingNotification.users.push({
user: notification.objectID,
timeStamp: new Date()
});
existingNotification.lastUpdated = new Date();
existingNotification.link = notification.link;
existingNotification.objectID = notification.objectID;
return await existingNotification.save();
}
else {
let newNotification = new NOTIFICATION({
pid: notification.pid,
type: notification.type,
users: [{
user: notification.objectID,
timestamp: new Date()
}],
link: notification.link,
objectID: notification.objectID,
read: false,
lastUpdated: new Date()
});
await newNotification.save();
}
}*/
},
getFriends: async function(pid) {
const last10min = new Date(now.getTime() - 10 * 60 * 1000);
existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'follow', lastUpdated: { $gte: last10min } });
if (existingNotification) {
existingNotification.users.push({
user: notification.objectID,
timeStamp: now
});
existingNotification.lastUpdated = now;
existingNotification.link = notification.link;
existingNotification.objectID = notification.objectID;
existingNotification.read = false;
return await existingNotification.save();
} else {
const newNotification = new NOTIFICATION({
pid: notification.pid,
type: notification.type,
users: [{
user: notification.objectID,
timestamp: now
}],
link: notification.link,
objectID: notification.objectID,
read: false,
lastUpdated: now
});
await newNotification.save();
}
}
/*else if(notification.type === 'yeah') {
// { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` }
let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID })
if(existingNotification) {
existingNotification.lastUpdated = new Date();
return await existingNotification.save();
}
existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'yeah' });
if(existingNotification) {
existingNotification.users.push({
user: notification.objectID,
timeStamp: new Date()
});
existingNotification.lastUpdated = new Date();
existingNotification.link = notification.link;
existingNotification.objectID = notification.objectID;
return await existingNotification.save();
}
else {
let newNotification = new NOTIFICATION({
pid: notification.pid,
type: notification.type,
users: [{
user: notification.objectID,
timestamp: new Date()
}],
link: notification.link,
objectID: notification.objectID,
read: false,
lastUpdated: new Date()
});
await newNotification.save();
}
}*/
}
async function getFriends(pid) {
try {
const pids = await friendsClient.getUserFriendPIDs({
pid: pid
}, {
@@ -388,8 +388,12 @@ const methods = {
})
});
return pids.pids;
},
getFriendRequests: async function(pid) {
} catch (e) {
return [];
}
}
async function getFriendRequests(pid) {
try {
const requests = await friendsClient.getUserFriendRequestsIncoming({
pid: pid
}, {
@@ -398,47 +402,74 @@ const methods = {
})
});
return requests.friendRequests;
},
login: async function(username, password) {
return await apiClient.login({
username: username,
password: password,
grantType: 'password'
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
},
refreshLogin: async function(refreshToken) {
return await apiClient.login({
refreshToken: refreshToken
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
},
getUserDataFromToken: async function(token) {
return apiClient.getUserData({}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey,
'X-Token': token
})
});
},
getUserDataFromPid: async function(pid) {
return accountClient.getUserData({
pid: pid
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
},
getPid: async function(token) {
const user = await this.getUserDataFromToken(token);
return user.pid;
} catch (e) {
return [];
}
}
async function login(username, password) {
return await apiClient.login({
username: username,
password: password,
grantType: 'password'
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
}
async function refreshLogin(refreshToken) {
return await apiClient.login({
refreshToken: refreshToken
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
}
async function getUserDataFromToken(token) {
return apiClient.getUserData({}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey,
'X-Token': token
})
});
}
async function getUserDataFromPid(pid) {
return accountClient.getUserData({
pid: pid
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
}
async function getPid(token) {
const user = await this.getUserDataFromToken(token);
return user.pid;
}
module.exports = {
decodeParamPack,
processServiceToken,
decryptToken,
unpackToken,
processPainting,
nintendoPasswordHash,
getCommunityHash,
getUserHash,
refreshCache,
setName,
resizeImage,
createBMPTgaBuffer,
processLanguage,
uploadCDNAsset,
newNotification,
getFriends,
getFriendRequests,
login,
refreshLogin,
getUserDataFromToken,
getUserDataFromPid,
getPid,
create_user,
INVALID_POST_BODY_REGEX
};
exports.data = methods;