mirror of
https://github.com/PretendoNetwork/account.git
synced 2026-09-14 14:26:46 -05:00
feat(nnas): centralize error response creation
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
|
||||
const VALID_CLIENT_ID_SECRET_PAIRS: Record<string, string> = {
|
||||
// * 'Key' is the client ID, 'Value' is the client secret
|
||||
@@ -50,14 +50,14 @@ function nnasBasicHeaderCheckMiddleware(request: express.Request, response: expr
|
||||
|
||||
// * 0 = 3DS, 1 = Wii U
|
||||
if (platformID === undefined || (platformID !== '0' && platformID !== '1')) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'platformId format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -65,80 +65,80 @@ function nnasBasicHeaderCheckMiddleware(request: express.Request, response: expr
|
||||
// * 1 = debug, 2 = retail
|
||||
if (deviceType === undefined || (deviceType !== '1' && deviceType !== '2')) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'Device type format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (deviceID === undefined || !DEVICE_ID.test(deviceID)) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'deviceId format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (serialNumber === undefined || !SERIAL_REGEX.test(serialNumber)) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'serialNumber format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - Should the version check throw SYSTEM_UPDATE_REQUIRED?
|
||||
if (systemVersion === undefined || SYSTEM_VERSIONS[platformID] !== systemVersion) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'version format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (region === undefined || !REGIONS.includes(region)) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'X-Nintendo-Region format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (country === undefined) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'X-Nintendo-Country format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -150,112 +150,110 @@ function nnasBasicHeaderCheckMiddleware(request: express.Request, response: expr
|
||||
!VALID_CLIENT_ID_SECRET_PAIRS[clientID] ||
|
||||
clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientID]
|
||||
) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'client_id',
|
||||
code: '0004',
|
||||
message: 'API application invalid or incorrect application credentials'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (friendsVersion === undefined || friendsVersion !== '0000') {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'Friends version is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - Check this against valid list
|
||||
if (environment === undefined) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1017',
|
||||
message: 'The requested game environment wasn\'t found for the given game server.'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (titleID === undefined) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'Title ID format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (uniqueID === undefined) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'Unique ID format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (applicationVersion === undefined) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'Application version format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (platformID === '0' && model === undefined) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'Model format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (platformID === '0' && deviceCertificate === undefined) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from 'node:crypto';
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import NintendoCertificate from '@/nintendo-certificate';
|
||||
import { Device } from '@/models/device';
|
||||
|
||||
@@ -40,56 +40,56 @@ async function nnasCheckDeviceMiddleware(request: express.Request, response: exp
|
||||
|
||||
// * 3DS ALWAYS sends the device certificate
|
||||
if (platformID === '0' && deviceCertificate == undefined) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const shouldCheckCertificate = deviceCertificate !== undefined || platformID === '0' || REQUIRED_CERT_CHECK_ENDPOINTS.some(regex => regex.test(path));
|
||||
|
||||
if (shouldCheckCertificate) {
|
||||
if (deviceCertificate === undefined) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const certificate = new NintendoCertificate(deviceCertificate);
|
||||
|
||||
if (!certificate.valid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0110',
|
||||
message: 'Unlinked device'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const certificateDeviceID = parseInt(certificate.certificateName.slice(2).split('-')[0], 16);
|
||||
|
||||
if (deviceID !== certificateDeviceID) {
|
||||
// TODO - Change this to a different error
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let device = await Device.findOne({
|
||||
@@ -102,14 +102,14 @@ async function nnasCheckDeviceMiddleware(request: express.Request, response: exp
|
||||
// * the time the device document was created. Therefore we can
|
||||
// * know that serial tampering happened on the 3DS if this fails
|
||||
// * to find a device document.
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
code: '0002',
|
||||
message: 'serialNumber format is invalid'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0002',
|
||||
message: 'serialNumber format is invalid'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// * Update 3DS consoles to sync with the data from NASC
|
||||
@@ -139,15 +139,15 @@ async function nnasCheckDeviceMiddleware(request: express.Request, response: exp
|
||||
if (certificate.consoleType === '3ds') {
|
||||
// * If this happens, something has gone horribly wrong
|
||||
// TODO - Change this to a different error
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// * Assume device is a Wii U we've never seen before
|
||||
@@ -163,15 +163,15 @@ async function nnasCheckDeviceMiddleware(request: express.Request, response: exp
|
||||
if (device.serial !== serialNumber) {
|
||||
// * Spoofed serial. Device ID compared to certificate directly earlier
|
||||
// TODO - Change this to a different error
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
request.device = device;
|
||||
@@ -186,30 +186,26 @@ async function nnasCheckDeviceMiddleware(request: express.Request, response: exp
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'device_id',
|
||||
code: '0113',
|
||||
message: 'Unauthorized device'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (device.access_level < 0) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0012',
|
||||
message: 'Device has been banned by game server' // TODO - This is not the right error message
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
request.device = device;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { getValueFromHeaders } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { getPNIDByBasicAuth, getPNIDByNNASAccessToken } from '@/database';
|
||||
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
|
||||
@@ -28,55 +28,49 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon
|
||||
|
||||
if (!pnid) {
|
||||
if (type === 'Bearer') {
|
||||
response.status(401).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 401,
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0005',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.status(401).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 401,
|
||||
errors: [
|
||||
{
|
||||
code: '1105',
|
||||
message: 'Email address, username, or password, is not valid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (pnid.deleted) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0112',
|
||||
message: pnid.username
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (pnid.access_level < 0) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0108',
|
||||
message: 'Account has been banned'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
request.pnid = pnid;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { document as xmlParser } from 'xmlbuilder2';
|
||||
import { getValueFromHeaders, mapToObject } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
|
||||
function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void {
|
||||
if (request.method == 'POST' || request.method == 'PUT') {
|
||||
@@ -35,14 +35,15 @@ function XMLMiddleware(request: express.Request, response: express.Response, nex
|
||||
request.body = mapToObject(request.body);
|
||||
} catch (error) {
|
||||
// TODO - This is not a real error code, check to see if better one exists
|
||||
return response.status(401).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 401,
|
||||
errors: [
|
||||
{
|
||||
code: '0004',
|
||||
message: 'XML parse error'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
44
src/services/nnas/create-response.ts
Normal file
44
src/services/nnas/create-response.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
|
||||
// TODO - I'm unsure if this is the best place for this file to be or the best place to put these functions. It's fine for now though
|
||||
// TODO - Support JSON mode
|
||||
|
||||
type CreateNNASErrorResponseOptions = {
|
||||
status?: number;
|
||||
errors: {
|
||||
cause?: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
type CreateNNASResponseOptions = {
|
||||
status?: number;
|
||||
body: Record<string, any>;
|
||||
}
|
||||
|
||||
// TODO - This can largely be removed once an upgrade to Express v5 is done, since v5 supports throwing errors in async routes
|
||||
export function createNNASErrorResponse(response: express.Response, options: CreateNNASErrorResponseOptions): void {
|
||||
createNNASResponse(response, {
|
||||
status: options.status || 400,
|
||||
body: {
|
||||
// TODO - This is NOT suitable for JSON mode. Hack to get xmlbuilder to properly build XML error lists in the way we need it to. NNAS formats JSON error lists as `{"errors":[{"code":"0107","message":"Account country and device country do not match"}]}`
|
||||
errors: {
|
||||
error: options.errors
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function createNNASResponse(response: express.Response, options: CreateNNASResponseOptions): void {
|
||||
response.set('Content-Type', 'text/xml');
|
||||
response.set('Server', 'Nintendo 3DS (http)');
|
||||
response.set('X-Nintendo-Date', new Date().getTime().toString());
|
||||
|
||||
const body = xmlbuilder
|
||||
.create(options.body)
|
||||
.commentBefore('WARNING! DO NOT SHARE ANYTHING IN THIS REQUEST OR RESPONSE WITH UNTRUSTED USERS! REQUESTS AND RESPONSES CONTAIN SENSITIVE INFORMATION ABOUT YOUR DEVICE/ACCOUNT SUCH AS PASSWORDS, EMAILS, CERTIFICATES, ETC!').end();
|
||||
|
||||
response.status(options.status || 200).send(body);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { getValueFromQueryString } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { PNID } from '@/models/pnid';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -16,17 +17,15 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res
|
||||
const input = getValueFromQueryString(request.query, 'input');
|
||||
|
||||
if (!inputType || !outputType || !input) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let inputList = input.split(',');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { getValueFromQueryString } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { PNID } from '@/models/pnid';
|
||||
import { config } from '@/config-manager';
|
||||
import { YesNoBoolString } from '@/types/common/yes-no-bool-string';
|
||||
@@ -16,17 +17,15 @@ router.get('/', async (request: express.Request, response: express.Response): Pr
|
||||
const input = getValueFromQueryString(request.query, 'pids');
|
||||
|
||||
if (!input) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const pids = input.split(',').map(pid => Number(pid)).filter(pid => !isNaN(pid));
|
||||
|
||||
@@ -3,6 +3,7 @@ import xmlbuilder from 'xmlbuilder';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getPNIDByNNASRefreshToken, getPNIDByUsername } from '@/database';
|
||||
import { generateOAuthTokens } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { Device } from '@/models/device';
|
||||
import { SystemType } from '@/types/common/token';
|
||||
|
||||
@@ -20,95 +21,97 @@ router.post('/access_token/generate', async (request: express.Request, response:
|
||||
const refreshToken = request.body.refresh_token;
|
||||
|
||||
if (!['password', 'refresh_token'].includes(grantType)) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'grant_type',
|
||||
code: '0004',
|
||||
message: 'Invalid Grant Type'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'grant_type',
|
||||
code: '0004',
|
||||
message: 'Invalid Grant Type'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let pnid = null;
|
||||
|
||||
if (grantType === 'password') {
|
||||
if (!username || username.trim() === '') {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'user_id',
|
||||
code: '0002',
|
||||
message: 'user_id format is invalid'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'user_id',
|
||||
code: '0002',
|
||||
message: 'user_id format is invalid'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!password || password.trim() === '') {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'password',
|
||||
code: '0002',
|
||||
message: 'password format is invalid'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'password',
|
||||
code: '0002',
|
||||
message: 'password format is invalid'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
pnid = await getPNIDByUsername(username);
|
||||
|
||||
// TODO - Client also sends a password_type field, which is always set to "hashed". If field is missing or not "hashed", assume input password is plain-text
|
||||
if (!pnid || !await bcrypt.compare(password, pnid.password)) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0106',
|
||||
message: 'Invalid account ID or password'
|
||||
}
|
||||
}
|
||||
}).end({ pretty: true }));
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!refreshToken || refreshToken.trim() === '') {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'refresh_token',
|
||||
code: '0106',
|
||||
message: 'Invalid Refresh Token'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 401,
|
||||
errors: [
|
||||
{
|
||||
cause: 'refresh_token',
|
||||
code: '0106',
|
||||
message: 'Invalid Refresh Token'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
pnid = await getPNIDByNNASRefreshToken(refreshToken);
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 401,
|
||||
errors: [
|
||||
{
|
||||
cause: 'refresh_token',
|
||||
code: '0106',
|
||||
message: 'Invalid Refresh Token'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 401,
|
||||
errors: [
|
||||
{
|
||||
cause: 'refresh_token',
|
||||
code: '0106',
|
||||
message: 'Invalid Refresh Token'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'refresh_token',
|
||||
code: '0106',
|
||||
message: 'Invalid Refresh Token'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,14 +119,14 @@ router.post('/access_token/generate', async (request: express.Request, response:
|
||||
// * 0112 is the "account deleted" error, but unsure if this unlinks the PNID from the user?
|
||||
// * 0143 is the "The link to this Nintendo Network ID has been temporarliy removed" error,
|
||||
// * maybe that is a better error to use here?
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
code: '0112',
|
||||
message: pnid.username
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0112',
|
||||
message: pnid.username
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// * This are set/validated in consoleStatusVerificationMiddleware
|
||||
@@ -139,16 +142,14 @@ router.post('/access_token/generate', async (request: express.Request, response:
|
||||
}
|
||||
|
||||
if (pnid.access_level < 0) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0108',
|
||||
message: 'Account has been banned'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@ import moment from 'moment';
|
||||
import ratelimit from '@/middleware/ratelimit';
|
||||
import { connection as databaseConnection, doesPNIDExist, getPNIDProfileJSONByPID } from '@/database';
|
||||
import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail, sendPNIDDeletedEmail } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { PNID } from '@/models/pnid';
|
||||
import { NEXAccount } from '@/models/nex-account';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
@@ -28,16 +29,14 @@ router.get('/:username', async (request: express.Request, response: express.Resp
|
||||
const userExists = await doesPNIDExist(username);
|
||||
|
||||
if (userExists) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0100',
|
||||
message: 'Account ID already exists'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send();
|
||||
@@ -54,16 +53,14 @@ router.post('/', ratelimit, async (request: express.Request, response: express.R
|
||||
const userExists = await doesPNIDExist(person.user_id);
|
||||
|
||||
if (userExists) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0100',
|
||||
message: 'Account ID already exists'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS');
|
||||
@@ -168,15 +165,15 @@ router.post('/', ratelimit, async (request: express.Request, response: express.R
|
||||
|
||||
await session.abortTransaction();
|
||||
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
error: {
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
]
|
||||
});
|
||||
} finally {
|
||||
// * This runs regardless of failure
|
||||
// * Returning on catch will not prevent this from running
|
||||
@@ -206,34 +203,32 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const person = await getPNIDProfileJSONByPID(pnid.pid);
|
||||
|
||||
if (!person) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send(xmlbuilder.create({
|
||||
@@ -261,34 +256,32 @@ router.post('/@me/devices', async (request: express.Request, response: express.R
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const person = await getPNIDProfileJSONByPID(pnid.pid);
|
||||
|
||||
if (!person) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send(xmlbuilder.create({
|
||||
@@ -316,32 +309,29 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re
|
||||
|
||||
if (!deviceID || !acceptLanguage || !platformID || !region || !serialNumber || !systemVersion) {
|
||||
// TODO - Research these error more
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Bad Request',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send(xmlbuilder.create({
|
||||
@@ -379,34 +369,32 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const person = await getPNIDProfileJSONByPID(pnid.pid);
|
||||
|
||||
if (!person) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send(xmlbuilder.create({
|
||||
@@ -440,17 +428,16 @@ router.put('/@me/miis/@primary', async (request: express.Request, response: expr
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Research this error more
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const mii: {
|
||||
@@ -482,17 +469,15 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request,
|
||||
const pnid = request.pnid;
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send();
|
||||
@@ -507,17 +492,15 @@ router.post('/@me/deletion', async (request: express.Request, response: express.
|
||||
const pnid = request.pnid;
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const email = pnid.email.address;
|
||||
@@ -544,17 +527,15 @@ router.put('/@me', async (request: express.Request, response: express.Response):
|
||||
const person: Person = request.body.person;
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const gender = person.gender ? person.gender : pnid.gender;
|
||||
@@ -618,17 +599,15 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res
|
||||
const pnid = request.pnid;
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send(xmlbuilder.create({
|
||||
@@ -663,17 +642,15 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex
|
||||
} = request.body.email;
|
||||
|
||||
if (!pnid || !email || !email.address) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// TODO - Better email check
|
||||
|
||||
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { getServerByClientID, getServerByGameServerID } from '@/database';
|
||||
import { generateToken, getValueFromHeaders, getValueFromQueryString, isSystemType } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { NEXAccount } from '@/models/nex-account';
|
||||
import { SystemType, TokenOptions, TokenType } from '@/types/common/token';
|
||||
|
||||
@@ -16,78 +17,68 @@ router.get('/service_token/@me', async (request: express.Request, response: expr
|
||||
const pnid = request.pnid;
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const clientID = getValueFromQueryString(request.query, 'client_id');
|
||||
|
||||
if (!clientID) {
|
||||
// TODO - Research this error more
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1021',
|
||||
message: 'The requested game server was not found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const titleID = getValueFromHeaders(request.headers, 'x-nintendo-title-id');
|
||||
|
||||
if (!titleID) {
|
||||
// TODO - Research this error more
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1021',
|
||||
message: 'The requested game server was not found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const serverAccessLevel = pnid.server_access_level;
|
||||
const server = await getServerByClientID(clientID, serverAccessLevel);
|
||||
|
||||
if (!server || !server.aes_key) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1021',
|
||||
message: 'The requested game server was not found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (server.maintenance_mode) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '2002',
|
||||
message: 'The requested game server is under maintenance'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!isSystemType(server.device)) {
|
||||
@@ -125,17 +116,15 @@ router.get('/nex_token/@me', async (request: express.Request, response: express.
|
||||
const pnid = request.pnid;
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'access_token',
|
||||
code: '0002',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const nexAccount = await NEXAccount.findOne({
|
||||
@@ -143,77 +132,68 @@ router.get('/nex_token/@me', async (request: express.Request, response: express.
|
||||
});
|
||||
|
||||
if (!nexAccount) {
|
||||
response.status(404).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
status: 404,
|
||||
errors: [
|
||||
{
|
||||
cause: '',
|
||||
code: '0008',
|
||||
message: 'Not Found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const gameServerID = getValueFromQueryString(request.query, 'game_server_id');
|
||||
|
||||
if (!gameServerID) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0118',
|
||||
message: 'Unique ID and Game Server ID are not linked'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const serverAccessLevel = pnid.server_access_level;
|
||||
const server = await getServerByGameServerID(gameServerID, serverAccessLevel);
|
||||
|
||||
if (!server || !server.aes_key) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1021',
|
||||
message: 'The requested game server was not found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (server.maintenance_mode) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '2002',
|
||||
message: 'The requested game server is under maintenance'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const titleID = getValueFromHeaders(request.headers, 'x-nintendo-title-id');
|
||||
|
||||
if (!titleID) {
|
||||
// TODO - Research this error more
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1021',
|
||||
message: 'The requested game server was not found'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!isSystemType(server.device)) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import dns from 'node:dns';
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import moment from 'moment';
|
||||
import { getPNIDByEmailAddress, getPNIDByPID } from '@/database';
|
||||
import { Device } from '@/models/device';
|
||||
import { sendEmailConfirmedEmail, sendConfirmationEmail, sendForgotPasswordEmail, sendEmailConfirmedParentalControlsEmail } from '@/util';
|
||||
import { createNNASErrorResponse } from '@/services/nnas/create-response';
|
||||
import { Device } from '@/models/device';
|
||||
|
||||
// * Middleware to ensure the input device is valid
|
||||
// TODO - Make this available for more routes? This could be useful elsewhere
|
||||
@@ -23,30 +23,26 @@ async function validateDeviceIDMiddleware(request: express.Request, response: ex
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'device_id',
|
||||
code: '0113',
|
||||
message: 'Unauthorized device'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (device.access_level < 0) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0012',
|
||||
message: 'Device has been banned by game server' // TODO - This is not the right error message
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// TODO - Once we push support for linking PNIDs to consoles, also check if the PID is linked or not
|
||||
@@ -65,31 +61,29 @@ router.post('/validate/email', async (request: express.Request, response: expres
|
||||
const email = request.body.email;
|
||||
|
||||
if (!email) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'email',
|
||||
code: '0103',
|
||||
message: 'Email format is invalid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const domain = email.split('@')[1];
|
||||
|
||||
dns.resolveMx(domain, (error: NodeJS.ErrnoException | null) => {
|
||||
if (error) {
|
||||
return response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '1126',
|
||||
message: 'The domain "' + domain + '" is not accessible.'
|
||||
message: `The domain "${domain}" is not accessible.`
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
response.send();
|
||||
@@ -108,16 +102,14 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re
|
||||
const pnid = await getPNIDByPID(pid);
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0130',
|
||||
message: 'PID has not been registered yet'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// * If the email is already confirmed don't bother continuing
|
||||
@@ -128,15 +120,14 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re
|
||||
}
|
||||
|
||||
if (pnid.identification.email_code !== code) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0116',
|
||||
message: 'Missing or invalid verification code'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const validatedDate = moment().format('YYYY-MM-DDTHH:MM:SS');
|
||||
@@ -164,16 +155,14 @@ router.get('/resend_confirmation', validateDeviceIDMiddleware, async (request: e
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0130',
|
||||
message: 'PID has not been registered yet'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// * If the email is already confirmed don't bother continuing
|
||||
@@ -200,16 +189,14 @@ router.get('/send_confirmation/pin/:email', async (request: express.Request, res
|
||||
|
||||
if (!pnid) {
|
||||
// TODO - Unsure if this is the right error
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
code: '0130',
|
||||
message: 'PID has not been registered yet'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
await sendEmailConfirmedParentalControlsEmail(pnid);
|
||||
@@ -226,17 +213,15 @@ router.get('/send_confirmation/pin/:email', async (request: express.Request, res
|
||||
router.get('/forgotten_password/:pid', validateDeviceIDMiddleware, async (request: express.Request, response: express.Response): Promise<void> => {
|
||||
if (!/^\d+$/.test(request.params.pid)) {
|
||||
// * This is what Nintendo sends
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
return createNNASErrorResponse(response, {
|
||||
errors: [
|
||||
{
|
||||
cause: 'Not Found',
|
||||
code: '1600',
|
||||
message: 'Unable to process request'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const pid = Number(request.params.pid);
|
||||
|
||||
Reference in New Issue
Block a user