diff --git a/.gitignore b/.gitignore
index bc708df..2e8b69a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,65 +1,65 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (http://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# Typescript v1 declaration files
-typings/
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variables file
-.env
-
-# custom
-sign.js
-t.js
-config.json
-servers.json
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# custom
+sign.js
+t.js
+config.json
+servers.json
certs
\ No newline at end of file
diff --git a/create-test-user.js b/create-test-user.js
index 9597b0f..494d22e 100644
--- a/create-test-user.js
+++ b/create-test-user.js
@@ -1,79 +1,79 @@
-const prompt = require('prompt');
-const crypto = require('crypto');
-const util = require('./src/util');
-const database = require('./src/database');
-const { PNID } = require('./src/models/pnid');
-
-prompt.message = '';
-
-const properties = [
- 'username',
- 'email',
- {
- name: 'password',
- hidden: true
- }
-];
-
-prompt.get(properties, function (error, { username, email, password }) {
- const date = new Date().toISOString();
- const miiHash = crypto.createHash('md5').update(date).digest('hex');
-
- const document = {
- pid: 1,
- creation_date: date.split('.')[0],
- updated: date,
- username: username,
- password: password,
- birthdate: '1990-01-01',
- gender: 'M',
- country: 'US',
- language: 'en',
- email: {
- address: email,
- primary: true,
- parent: true,
- reachable: true,
- validated: true,
- id: util.generateRandomInt(10)
- },
- region: 0x310B0000,
- timezone: {
- name: 'America/New_York',
- offset: -14400
- },
- mii: {
- name: 'UserMii',
- primary: true,
- data: 'AwAAQIhluwTgxEAA2NlGWQOzuI0n2QAAAEBsAG8AZwBpAG4AdABlAHMAdAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMw7', // hardcoded for now. currently testing
- id: util.generateRandomInt(10),
- hash: miiHash,
- image_url: 'https://mii-secure.account.nintendo.net/2rtgf01lztoqo_standard.tga',
- image_id: util.generateRandomInt(10)
- },
- flags: {
- active: true,
- marketing: false,
- off_device: true
- },
- validation: {
- // These values are temp and will be overwritten before the document saves
- // These values are only being defined to get around the `E11000 duplicate key error collection` error
- email_code: Date.now(),
- email_token: Date.now().toString()
- }
- };
-
- const newUser = new PNID(document);
-
- newUser.save(async (error, newUser) => {
- if (error) {
- throw error;
- }
-
- console.log(newUser);
- console.log('New user created');
- });
-});
-
+const prompt = require('prompt');
+const crypto = require('crypto');
+const util = require('./src/util');
+const database = require('./src/database');
+const { PNID } = require('./src/models/pnid');
+
+prompt.message = '';
+
+const properties = [
+ 'username',
+ 'email',
+ {
+ name: 'password',
+ hidden: true
+ }
+];
+
+prompt.get(properties, function (error, { username, email, password }) {
+ const date = new Date().toISOString();
+ const miiHash = crypto.createHash('md5').update(date).digest('hex');
+
+ const document = {
+ pid: 1,
+ creation_date: date.split('.')[0],
+ updated: date,
+ username: username,
+ password: password,
+ birthdate: '1990-01-01',
+ gender: 'M',
+ country: 'US',
+ language: 'en',
+ email: {
+ address: email,
+ primary: true,
+ parent: true,
+ reachable: true,
+ validated: true,
+ id: util.generateRandomInt(10)
+ },
+ region: 0x310B0000,
+ timezone: {
+ name: 'America/New_York',
+ offset: -14400
+ },
+ mii: {
+ name: 'bella',
+ primary: true,
+ data: 'AwAAQOlVognnx0GC2X0LLQOzuI0n2QAAAUBiAGUAbABsAGEAAABFAAAAAAAAAEBAEgCBAQRoQxggNEYUgRIXaA0AACkDUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP6G', // hardcoded for now. currently testing
+ id: 1330180812,
+ hash: '1o54mkuuxfg8t',
+ image_url: 'https://mii-secure.account.nintendo.net/1o54mkuuxfg8t_standard.tga',
+ image_id: 1330180887
+ },
+ flags: {
+ active: true,
+ marketing: false,
+ off_device: true
+ },
+ validation: {
+ // These values are temp and will be overwritten before the document saves
+ // These values are only being defined to get around the `E11000 duplicate key error collection` error
+ email_code: Date.now(),
+ email_token: Date.now().toString()
+ }
+ };
+
+ const newUser = new PNID(document);
+
+ newUser.save(async (error, newUser) => {
+ if (error) {
+ throw error;
+ }
+
+ console.log(newUser);
+ console.log('New user created');
+ });
+});
+
database.connect().then(prompt.start);
\ No newline at end of file
diff --git a/generate-keys.js b/generate-keys.js
index b6e7b98..09fce5c 100644
--- a/generate-keys.js
+++ b/generate-keys.js
@@ -1,88 +1,88 @@
-const NodeRSA = require('node-rsa');
-const crypto = require('crypto');
-const fs = require('fs-extra');
-require('colors');
-
-const args = process.argv.slice(2);
-
-if (args.length < 1) {
- usage();
- return;
-}
-
-const [type, name] = args;
-
-if (!['nex', 'service', 'access'].includes(type)) {
- usage();
- return;
-}
-
-if (type !== 'access' && (!name || name.trim() === '')) {
- usage();
- return;
-}
-
-let path;
-
-if (type === 'access') {
- path = `${__dirname}/certs/${type}`;
-} else {
- path = `${__dirname}/certs/${type}/${name}`;
-}
-
-// Ensure the output directories exist
-console.log('Creating output directories'.brightGreen);
-fs.ensureDirSync(path);
-
-// Generate new AES key
-console.log('Generating AES key'.brightGreen);
-const aesKey = crypto.randomBytes(16);
-
-// Saving AES key
-fs.writeFileSync(`${path}/aes.key`, aesKey.toString('hex'));
-console.log(`Saved AES key to file ${path}/aes.key`.brightBlue);
-
-const key = new NodeRSA({ b: 1024}, null, {
- environment: 'browser',
- encryptionScheme: {
- 'hash': 'sha256',
- }
-});
-
-// Generate new key pair
-console.log('Generating RSA key pair'.brightGreen, '(this may take a while)'.yellow.bold);
-key.generateKeyPair(1024);
-
-// Export the keys
-console.log('Exporting public key'.brightGreen);
-const publickKey = key.exportKey('public');
-
-// Saving public key
-fs.writeFileSync(`${path}/public.pem`, publickKey);
-console.log(`Saved public key to file ${path}/public.pem`.brightBlue);
-
-console.log('Exporting private key'.brightGreen);
-const privatekKey = key.exportKey('private');
-
-// Saving private key
-fs.writeFileSync(`${path}/private.pem`, privatekKey);
-console.log(`Saved public key to file ${path}/private.pem`.brightBlue);
-
-// Create HMAC secret key
-console.log('Generating HMAC secret'.brightGreen);
-const secret = crypto.randomBytes(16);
-fs.writeFileSync(`${path}/secret.key`, secret.toString('hex'));
-
-console.log(`Saved HMAC secret to file ${path}/secret.key`.brightBlue);
-
-// Display usage information
-function usage() {
- console.log('Usage: node generate-keys.js type [name]');
-
- console.log('Types:');
- console.log(' - nex');
- console.log(' - service');
- console.log(' - access');
-
- console.log('Name: service or nex server name. Not used in access type');
+const NodeRSA = require('node-rsa');
+const crypto = require('crypto');
+const fs = require('fs-extra');
+require('colors');
+
+const args = process.argv.slice(2);
+
+if (args.length < 1) {
+ usage();
+ return;
+}
+
+const [type, name] = args;
+
+if (!['nex', 'service', 'access'].includes(type)) {
+ usage();
+ return;
+}
+
+if (type !== 'access' && (!name || name.trim() === '')) {
+ usage();
+ return;
+}
+
+let path;
+
+if (type === 'access') {
+ path = `${__dirname}/certs/${type}`;
+} else {
+ path = `${__dirname}/certs/${type}/${name}`;
+}
+
+// Ensure the output directories exist
+console.log('Creating output directories'.brightGreen);
+fs.ensureDirSync(path);
+
+// Generate new AES key
+console.log('Generating AES key'.brightGreen);
+const aesKey = crypto.randomBytes(16);
+
+// Saving AES key
+fs.writeFileSync(`${path}/aes.key`, aesKey.toString('hex'));
+console.log(`Saved AES key to file ${path}/aes.key`.brightBlue);
+
+const key = new NodeRSA({ b: 1024}, null, {
+ environment: 'browser',
+ encryptionScheme: {
+ 'hash': 'sha256',
+ }
+});
+
+// Generate new key pair
+console.log('Generating RSA key pair'.brightGreen, '(this may take a while)'.yellow.bold);
+key.generateKeyPair(1024);
+
+// Export the keys
+console.log('Exporting public key'.brightGreen);
+const publickKey = key.exportKey('public');
+
+// Saving public key
+fs.writeFileSync(`${path}/public.pem`, publickKey);
+console.log(`Saved public key to file ${path}/public.pem`.brightBlue);
+
+console.log('Exporting private key'.brightGreen);
+const privatekKey = key.exportKey('private');
+
+// Saving private key
+fs.writeFileSync(`${path}/private.pem`, privatekKey);
+console.log(`Saved public key to file ${path}/private.pem`.brightBlue);
+
+// Create HMAC secret key
+console.log('Generating HMAC secret'.brightGreen);
+const secret = crypto.randomBytes(16);
+fs.writeFileSync(`${path}/secret.key`, secret.toString('hex'));
+
+console.log(`Saved HMAC secret to file ${path}/secret.key`.brightBlue);
+
+// Display usage information
+function usage() {
+ console.log('Usage: node generate-keys.js type [name]');
+
+ console.log('Types:');
+ console.log(' - nex');
+ console.log(' - service');
+ console.log(' - access');
+
+ console.log('Name: service or nex server name. Not used in access type');
}
\ No newline at end of file
diff --git a/logger.js b/logger.js
index eda058e..07215de 100644
--- a/logger.js
+++ b/logger.js
@@ -1,52 +1,52 @@
-const fs = require('fs-extra');
-require('colors');
-
-const root = __dirname;
-fs.ensureDirSync(`${root}/logs`);
-
-const streams = {
- latest: fs.createWriteStream(`${root}/logs/latest.log`),
- success: fs.createWriteStream(`${root}/logs/success.log`),
- error: fs.createWriteStream(`${root}/logs/error.log`),
- warn: fs.createWriteStream(`${root}/logs/warn.log`),
- info: fs.createWriteStream(`${root}/logs/info.log`)
-};
-
-function success(input) {
- const time = new Date();
- input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
- streams.success.write(`${input}\n`);
-
- console.log(`${input}`.green.bold);
-}
-
-function error(input) {
- const time = new Date();
- input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
- streams.error.write(`${input}\n`);
-
- console.log(`${input}`.red.bold);
-}
-
-function warn(input) {
- const time = new Date();
- input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
- streams.warn.write(`${input}\n`);
-
- console.log(`${input}`.yellow.bold);
-}
-
-function info(input) {
- const time = new Date();
- input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
- streams.info.write(`${input}\n`);
-
- console.log(`${input}`.cyan.bold);
-}
-
-module.exports = {
- success,
- error,
- warn,
- info
+const fs = require('fs-extra');
+require('colors');
+
+const root = __dirname;
+fs.ensureDirSync(`${root}/logs`);
+
+const streams = {
+ latest: fs.createWriteStream(`${root}/logs/latest.log`),
+ success: fs.createWriteStream(`${root}/logs/success.log`),
+ error: fs.createWriteStream(`${root}/logs/error.log`),
+ warn: fs.createWriteStream(`${root}/logs/warn.log`),
+ info: fs.createWriteStream(`${root}/logs/info.log`)
+};
+
+function success(input) {
+ const time = new Date();
+ input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
+ streams.success.write(`${input}\n`);
+
+ console.log(`${input}`.green.bold);
+}
+
+function error(input) {
+ const time = new Date();
+ input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
+ streams.error.write(`${input}\n`);
+
+ console.log(`${input}`.red.bold);
+}
+
+function warn(input) {
+ const time = new Date();
+ input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
+ streams.warn.write(`${input}\n`);
+
+ console.log(`${input}`.yellow.bold);
+}
+
+function info(input) {
+ const time = new Date();
+ input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
+ streams.info.write(`${input}\n`);
+
+ console.log(`${input}`.cyan.bold);
+}
+
+module.exports = {
+ success,
+ error,
+ warn,
+ info
};
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index cdc154f..c6bdfc4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -154,12 +154,12 @@
}
},
"bcrypt": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.7.tgz",
- "integrity": "sha512-K5UglF9VQvBMHl/1elNyyFvAfOY9Bj+rpKrCSR9sFwcW8FywAYJSRwTURNej5TaAK2TEJkcJ6r6lh1YPmspx5Q==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz",
+ "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==",
"requires": {
- "nan": "2.14.0",
- "node-pre-gyp": "0.13.0"
+ "node-addon-api": "^3.0.0",
+ "node-pre-gyp": "0.15.0"
}
},
"bluebird": {
@@ -204,9 +204,9 @@
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"chownr": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
- "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw=="
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"code-point-at": {
"version": "1.1.0",
@@ -356,33 +356,6 @@
"vary": "~1.1.2"
}
},
- "express-session": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz",
- "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==",
- "requires": {
- "cookie": "0.4.0",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "~2.0.0",
- "on-headers": "~1.0.2",
- "parseurl": "~1.3.3",
- "safe-buffer": "5.2.0",
- "uid-safe": "~2.1.5"
- },
- "dependencies": {
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
- },
- "safe-buffer": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
- "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
- }
- }
- },
"express-subdomain": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/express-subdomain/-/express-subdomain-1.0.5.tgz",
@@ -526,9 +499,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"ipaddr.js": {
"version": "1.9.0",
@@ -624,9 +597,9 @@
}
},
"minimist": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
- "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minipass": {
"version": "2.9.0",
@@ -646,11 +619,11 @@
}
},
"mkdirp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
- "minimist": "0.0.8"
+ "minimist": "^1.2.5"
}
},
"moment": {
@@ -765,20 +738,15 @@
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
- "nan": {
- "version": "2.14.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
- "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
- },
"ncp": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz",
"integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY="
},
"needle": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
- "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz",
+ "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==",
"requires": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
@@ -786,17 +754,17 @@
},
"dependencies": {
"debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
@@ -805,21 +773,26 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
+ "node-addon-api": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz",
+ "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw=="
+ },
"node-pre-gyp": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz",
- "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==",
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz",
+ "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==",
"requires": {
"detect-libc": "^1.0.2",
- "mkdirp": "^0.5.1",
- "needle": "^2.2.1",
+ "mkdirp": "^0.5.3",
+ "needle": "^2.5.0",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
- "tar": "^4"
+ "tar": "^4.4.2"
}
},
"node-rsa": {
@@ -836,9 +809,9 @@
"integrity": "sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ=="
},
"nopt": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
- "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
+ "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
@@ -858,12 +831,13 @@
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
},
"npm-packlist": {
- "version": "1.4.7",
- "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz",
- "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==",
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
+ "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
"requires": {
"ignore-walk": "^3.0.1",
- "npm-bundled": "^1.0.1"
+ "npm-bundled": "^1.0.1",
+ "npm-normalize-package-bin": "^1.0.1"
}
},
"npmlog": {
@@ -984,11 +958,6 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
- "random-bytes": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
- "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
- },
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1014,13 +983,6 @@
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
- }
}
},
"read": {
@@ -1032,9 +994,9 @@
}
},
"readable-stream": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -1160,9 +1122,9 @@
"integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g=="
},
"signal-exit": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
- "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
"sliced": {
"version": "1.0.1",
@@ -1247,14 +1209,6 @@
"mime-types": "~2.1.24"
}
},
- "uid-safe": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
- "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
- "requires": {
- "random-bytes": "~1.0.0"
- }
- },
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
diff --git a/package.json b/package.json
index a76e42d..c44b7fa 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "account",
"version": "1.0.0",
"description": "",
- "main": "./src/server",
+ "main": "./src/server.js",
"scripts": {
"lint": "./node_modules/.bin/eslint .",
"start": "node .",
@@ -20,10 +20,9 @@
},
"homepage": "https://github.com/PretendoNetwork/account#readme",
"dependencies": {
- "bcrypt": "^3.0.7",
+ "bcrypt": "^5.0.0",
"colors": "^1.4.0",
"express": "^4.17.1",
- "express-session": "^1.17.0",
"express-subdomain": "^1.0.5",
"fs-extra": "^8.1.0",
"moment": "^2.24.0",
diff --git a/src/database.js b/src/database.js
index fa3d9ab..a483710 100644
--- a/src/database.js
+++ b/src/database.js
@@ -1,170 +1,170 @@
-const mongoose = require('mongoose');
-const bcrypt = require('bcrypt');
-const util = require('./util');
-const { PNID } = require('./models/pnid');
-const { mongoose: mongooseConfig } = require('./config.json');
-const { uri, database, options } = mongooseConfig;
-
-let connection;
-
-async function connect() {
- await mongoose.connect(`${uri}/${database}`, options);
-
- connection = mongoose.connection;
- connection.on('error', console.error.bind(console, 'connection error:'));
-}
-
-function verifyConnected() {
- if (!connection) {
- throw new Error('Cannot make database requets without being connected');
- }
-}
-
-async function getUserByUsername(username) {
- verifyConnected();
-
- if (typeof username !== 'string') {
- return null;
- }
-
- const user = await PNID.findOne({
- usernameLower: username.toLowerCase()
- });
-
- return user;
-}
-
-async function getUserByPID(pid) {
- verifyConnected();
-
- const user = await PNID.findOne({
- pid
- });
-
- return user;
-}
-
-async function doesUserExist(username) {
- verifyConnected();
-
- return !!await this.getUserByUsername(username);
-}
-
-async function getUserBasic(token) {
- verifyConnected();
-
- const [username, password] = Buffer.from(token, 'base64').toString().split(' ');
- const user = await this.getUserByUsername(username);
-
- if (!user) {
- return null;
- }
-
- const hashedPassword = util.nintendoPasswordHash(password, user.pid);
-
- if (!bcrypt.compareSync(hashedPassword, user.password)) {
- return null;
- }
-
- return user;
-}
-
-async function getUserBearer(token) {
- verifyConnected();
-
- const decryptedToken = util.decryptToken(Buffer.from(token, 'base64'));
- const unpackedToken = util.unpackToken(decryptedToken);
-
- const user = await getUserByPID(unpackedToken.pid);
-
- if (user) {
- const expireTime = Math.floor((Number(unpackedToken.date) / 1000) + 3600);
-
- if (Math.floor(Date.now()/1000) > expireTime) {
- return null;
- }
- }
-
- return user;
-}
-
-async function getUserProfileJSONByPID(pid) {
- verifyConnected();
-
- const user = await this.getUserByPID(pid);
- const device = user.get('devices')[0]; // Just grab the first device
- let device_attributes;
-
- if (device) {
- device_attributes = device.get('device_attributes').map(({name, value, created_date}) => {
- const deviceAttributeDocument = {
- name,
- value
- };
-
- if (created_date) {
- deviceAttributeDocument.created_date = created_date;
- }
-
- return {
- device_attribute: deviceAttributeDocument
- };
- });
- }
-
- return {
- //accounts: {}, We need to figure this out, no idea what these values mean or what they do
- active_flag: user.get('flags.active') ? 'Y' : 'N',
- birth_date: user.get('birthdate'),
- country: user.get('country'),
- create_date: user.get('creation_date'),
- device_attributes: device_attributes,
- gender: user.get('gender'),
- language: user.get('language'),
- updated: user.get('updated'),
- marketing_flag: user.get('flags.marketing') ? 'Y' : 'N',
- off_device_flag: user.get('flags.off_device') ? 'Y' : 'N',
- pid: user.get('pid'),
- email: {
- address: user.get('email.address'),
- id: user.get('email.id'),
- parent: user.get('email.parent') ? 'Y' : 'N',
- primary: user.get('email.primary') ? 'Y' : 'N',
- reachable: user.get('email.reachable') ? 'Y' : 'N',
- type: 'DEFAULT',
- updated_by: 'USER', // Can also be INTERNAL WS, don't know the difference
- validated: user.get('email.validated') ? 'Y' : 'N',
- //validated_date: user.get('email.validated_date') // not used atm
- },
- mii: {
- status: 'COMPLETED',
- data: user.get('mii.data'),
- id: user.get('mii.id'),
- mii_hash: user.get('mii.hash'),
- mii_images: {
- mii_image: {
- cached_url: user.get('mii.image_url'),
- id: user.get('mii.image_id'),
- url: user.get('mii.image_url'),
- type: 'standard'
- }
- },
- name: user.get('mii.name'),
- primary: user.get('mii.primary') ? 'Y' : 'N',
- },
- region: user.get('region'),
- tz_name: user.get('timezone.name'),
- user_id: user.get('username'),
- utc_offset: user.get('timezone.offset')
- };
-}
-
-module.exports = {
- connect,
- getUserByUsername,
- getUserByPID,
- doesUserExist,
- getUserBasic,
- getUserBearer,
- getUserProfileJSONByPID,
+const mongoose = require('mongoose');
+const bcrypt = require('bcrypt');
+const util = require('./util');
+const { PNID } = require('./models/pnid');
+const { mongoose: mongooseConfig } = require('./config.json');
+const { uri, database, options } = mongooseConfig;
+
+let connection;
+
+async function connect() {
+ await mongoose.connect(`${uri}/${database}`, options);
+
+ connection = mongoose.connection;
+ connection.on('error', console.error.bind(console, 'connection error:'));
+}
+
+function verifyConnected() {
+ if (!connection) {
+ throw new Error('Cannot make database requets without being connected');
+ }
+}
+
+async function getUserByUsername(username) {
+ verifyConnected();
+
+ if (typeof username !== 'string') {
+ return null;
+ }
+
+ const user = await PNID.findOne({
+ usernameLower: username.toLowerCase()
+ });
+
+ return user;
+}
+
+async function getUserByPID(pid) {
+ verifyConnected();
+
+ const user = await PNID.findOne({
+ pid
+ });
+
+ return user;
+}
+
+async function doesUserExist(username) {
+ verifyConnected();
+
+ return !!await this.getUserByUsername(username);
+}
+
+async function getUserBasic(token) {
+ verifyConnected();
+
+ const [username, password] = Buffer.from(token, 'base64').toString().split(' ');
+ const user = await this.getUserByUsername(username);
+
+ if (!user) {
+ return null;
+ }
+
+ const hashedPassword = util.nintendoPasswordHash(password, user.pid);
+
+ if (!bcrypt.compareSync(hashedPassword, user.password)) {
+ return null;
+ }
+
+ return user;
+}
+
+async function getUserBearer(token) {
+ verifyConnected();
+
+ const decryptedToken = util.decryptToken(Buffer.from(token, 'base64'));
+ const unpackedToken = util.unpackToken(decryptedToken);
+
+ const user = await getUserByPID(unpackedToken.pid);
+
+ if (user) {
+ const expireTime = Math.floor((Number(unpackedToken.date) / 1000) + 3600);
+
+ if (Math.floor(Date.now()/1000) > expireTime) {
+ return null;
+ }
+ }
+
+ return user;
+}
+
+async function getUserProfileJSONByPID(pid) {
+ verifyConnected();
+
+ const user = await this.getUserByPID(pid);
+ const device = user.get('devices')[0]; // Just grab the first device
+ let device_attributes;
+
+ if (device) {
+ device_attributes = device.get('device_attributes').map(({name, value, created_date}) => {
+ const deviceAttributeDocument = {
+ name,
+ value
+ };
+
+ if (created_date) {
+ deviceAttributeDocument.created_date = created_date;
+ }
+
+ return {
+ device_attribute: deviceAttributeDocument
+ };
+ });
+ }
+
+ return {
+ //accounts: {}, We need to figure this out, no idea what these values mean or what they do
+ active_flag: user.get('flags.active') ? 'Y' : 'N',
+ birth_date: user.get('birthdate'),
+ country: user.get('country'),
+ create_date: user.get('creation_date'),
+ device_attributes: device_attributes,
+ gender: user.get('gender'),
+ language: user.get('language'),
+ updated: user.get('updated'),
+ marketing_flag: user.get('flags.marketing') ? 'Y' : 'N',
+ off_device_flag: user.get('flags.off_device') ? 'Y' : 'N',
+ pid: user.get('pid'),
+ email: {
+ address: user.get('email.address'),
+ id: user.get('email.id'),
+ parent: user.get('email.parent') ? 'Y' : 'N',
+ primary: user.get('email.primary') ? 'Y' : 'N',
+ reachable: user.get('email.reachable') ? 'Y' : 'N',
+ type: 'DEFAULT',
+ updated_by: 'USER', // Can also be INTERNAL WS, don't know the difference
+ validated: user.get('email.validated') ? 'Y' : 'N',
+ //validated_date: user.get('email.validated_date') // not used atm
+ },
+ mii: {
+ status: 'COMPLETED',
+ data: user.get('mii.data'),
+ id: user.get('mii.id'),
+ mii_hash: user.get('mii.hash'),
+ mii_images: {
+ mii_image: {
+ cached_url: user.get('mii.image_url'),
+ id: user.get('mii.image_id'),
+ url: user.get('mii.image_url'),
+ type: 'standard'
+ }
+ },
+ name: user.get('mii.name'),
+ primary: user.get('mii.primary') ? 'Y' : 'N',
+ },
+ region: user.get('region'),
+ tz_name: user.get('timezone.name'),
+ user_id: user.get('username'),
+ utc_offset: user.get('timezone.offset')
+ };
+}
+
+module.exports = {
+ connect,
+ getUserByUsername,
+ getUserByPID,
+ doesUserExist,
+ getUserBasic,
+ getUserBearer,
+ getUserProfileJSONByPID,
};
\ No newline at end of file
diff --git a/src/example.config.json b/src/example.config.json
index cb63e1d..b3a7e36 100644
--- a/src/example.config.json
+++ b/src/example.config.json
@@ -1,19 +1,19 @@
-{
- "http": {
- "port": 7070
- },
- "mongoose": {
- "uri": "mongodb://localhost:27017",
- "database": "database_name",
- "options": {
- "useNewUrlParser": true
- }
- },
- "email": {
- "service": "gmail",
- "auth": {
- "user": "email@address.com",
- "pass": "password"
- }
- }
+{
+ "http": {
+ "port": 7070
+ },
+ "mongoose": {
+ "uri": "mongodb://localhost:27017",
+ "database": "database_name",
+ "options": {
+ "useNewUrlParser": true
+ }
+ },
+ "email": {
+ "service": "gmail",
+ "auth": {
+ "user": "email@address.com",
+ "pass": "password"
+ }
+ }
}
\ No newline at end of file
diff --git a/src/example.servers.json b/src/example.servers.json
index 88ae5e2..be3e344 100644
--- a/src/example.servers.json
+++ b/src/example.servers.json
@@ -1,24 +1,24 @@
-[
- {
- "title_ids": [
- "000500301001500A",
- "000500301001510A",
- "000500301001520A",
- "0005001010001C00"
- ],
- "server_id": "00003200",
- "ip": "192.168.0.27",
- "port": "60000",
- "system": 1,
- "crypto": {
- "service": {
- "cek": "",
- "hmac_secret": ""
- },
- "nex": {
- "cek": "",
- "hmac_secret": ""
- }
- }
- }
+[
+ {
+ "title_ids": [
+ "000500301001500A",
+ "000500301001510A",
+ "000500301001520A",
+ "0005001010001C00"
+ ],
+ "server_id": "00003200",
+ "ip": "192.168.0.27",
+ "port": "60000",
+ "system": 1,
+ "crypto": {
+ "service": {
+ "cek": "",
+ "hmac_secret": ""
+ },
+ "nex": {
+ "cek": "",
+ "hmac_secret": ""
+ }
+ }
+ }
]
\ No newline at end of file
diff --git a/src/mailer.js b/src/mailer.js
index 68accbf..e0d93ca 100644
--- a/src/mailer.js
+++ b/src/mailer.js
@@ -1,33 +1,33 @@
-const nodemailer = require('nodemailer');
-const config = require('./config');
-
-const transporter = nodemailer.createTransport(config.email);
-
-/**
- * Sends an email with the specified subject and message to an email address
- * @param {String} email the destination email address
- * @param {String} subject The Subject of the email
- * @param {String} message The body of the email
- */
-async function send(email, subject = 'No email subject provided', message = 'No email body provided') {
- const options = {
- from: config.email.auth.user,
- to: email,
- subject: subject,
- html: message
- };
-
- return new Promise(resolve => {
- transporter.sendMail(options, (error) => {
- if (error) {
- console.warn(error);
- }
-
- return resolve();
- });
- });
-}
-
-module.exports = {
- send: send
+const nodemailer = require('nodemailer');
+const config = require('./config');
+
+const transporter = nodemailer.createTransport(config.email);
+
+/**
+ * Sends an email with the specified subject and message to an email address
+ * @param {String} email the destination email address
+ * @param {String} subject The Subject of the email
+ * @param {String} message The body of the email
+ */
+async function send(email, subject = 'No email subject provided', message = 'No email body provided') {
+ const options = {
+ from: config.email.auth.user,
+ to: email,
+ subject: subject,
+ html: message
+ };
+
+ return new Promise(resolve => {
+ transporter.sendMail(options, (error) => {
+ if (error) {
+ console.warn(error);
+ }
+
+ return resolve();
+ });
+ });
+}
+
+module.exports = {
+ send: send
};
\ No newline at end of file
diff --git a/src/middleware/client-header.js b/src/middleware/client-header.js
index e722968..1999223 100644
--- a/src/middleware/client-header.js
+++ b/src/middleware/client-header.js
@@ -1,38 +1,38 @@
-const xmlbuilder = require('xmlbuilder');
-
-const VALID_CLIENT_ID_SECRET_PAIRS = {
- // 'Key' is the client ID, 'Value' is the client secret
- 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // Possibly WiiU exclusive?
- 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // Possibly 3DS exclusive?
- 'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // Possibly 3DS exclusive?
-};
-
-
-function nintendoClientHeaderCheck(request, response, next) {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', new Date().getTime());
-
- const {headers} = request;
-
- if (
- !headers['x-nintendo-client-id'] ||
- !headers['x-nintendo-client-secret'] ||
- !VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] ||
- headers['x-nintendo-client-secret'] !== VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']]
- ) {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- cause: 'client_id',
- code: '0004',
- message: 'API application invalid or incorrect application credentials'
- }
- }
- }).end());
- }
-
- return next();
-}
-
+const xmlbuilder = require('xmlbuilder');
+
+const VALID_CLIENT_ID_SECRET_PAIRS = {
+ // 'Key' is the client ID, 'Value' is the client secret
+ 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // Possibly WiiU exclusive?
+ 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // Possibly 3DS exclusive?
+ 'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // Possibly 3DS exclusive?
+};
+
+
+function nintendoClientHeaderCheck(request, response, next) {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', new Date().getTime());
+
+ const {headers} = request;
+
+ if (
+ !headers['x-nintendo-client-id'] ||
+ !headers['x-nintendo-client-secret'] ||
+ !VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] ||
+ headers['x-nintendo-client-secret'] !== VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']]
+ ) {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ cause: 'client_id',
+ code: '0004',
+ message: 'API application invalid or incorrect application credentials'
+ }
+ }
+ }).end());
+ }
+
+ return next();
+}
+
module.exports = nintendoClientHeaderCheck;
\ No newline at end of file
diff --git a/src/middleware/pnid.js b/src/middleware/pnid.js
index 5d47cde..4a9946f 100644
--- a/src/middleware/pnid.js
+++ b/src/middleware/pnid.js
@@ -1,50 +1,50 @@
-const xmlbuilder = require('xmlbuilder');
-const database = require('../database');
-
-async function PNIDMiddleware(request, response, next) {
- const { headers } = request;
-
- if (!headers.authorization || !(headers.authorization.startsWith('Bearer') || headers.authorization.startsWith('Basic'))) {
- return next();
- }
-
- const [type, token] = headers.authorization.split(' ');
- let user;
-
- if (type === 'Basic') {
- user = await database.getUserBasic(token);
- } else {
- user = await database.getUserBearer(token);
- }
-
- if (!user) {
- response.status(401);
-
- if (type === 'Bearer') {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- cause: 'access_token',
- code: '0005',
- message: 'Invalid access token'
- }
- }
- }).end());
- }
-
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '1105',
- message: 'Email address, username, or password, is not valid'
- }
- }
- }).end());
- }
-
- request.pnid = user;
-
- return next();
-}
-
+const xmlbuilder = require('xmlbuilder');
+const database = require('../database');
+
+async function PNIDMiddleware(request, response, next) {
+ const { headers } = request;
+
+ if (!headers.authorization || !(headers.authorization.startsWith('Bearer') || headers.authorization.startsWith('Basic'))) {
+ return next();
+ }
+
+ const [type, token] = headers.authorization.split(' ');
+ let user;
+
+ if (type === 'Basic') {
+ user = await database.getUserBasic(token);
+ } else {
+ user = await database.getUserBearer(token);
+ }
+
+ if (!user) {
+ response.status(401);
+
+ if (type === 'Bearer') {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ cause: 'access_token',
+ code: '0005',
+ message: 'Invalid access token'
+ }
+ }
+ }).end());
+ }
+
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '1105',
+ message: 'Email address, username, or password, is not valid'
+ }
+ }
+ }).end());
+ }
+
+ request.pnid = user;
+
+ return next();
+}
+
module.exports = PNIDMiddleware;
\ No newline at end of file
diff --git a/src/middleware/session.js b/src/middleware/session.js
index e8b663f..93e383d 100644
--- a/src/middleware/session.js
+++ b/src/middleware/session.js
@@ -1,23 +1,23 @@
-// super basic and there's probably a much better way to do this
-
-// this will only be used during the registration process, to track the progress of the user
-// express-session uses cookies which the WiiU does not support during the registration process
-
-// temp, in-memory session storage
-const sessionStore = {};
-
-function sessionMiddlware(request, response, next) {
- const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
-
- if (!sessionStore[ip]) {
- sessionStore[ip] = {};
- }
-
- const session = sessionStore[ip];
-
- request.session = session;
-
- return next();
-}
-
+// super basic and there's probably a much better way to do this
+
+// this will only be used during the registration process, to track the progress of the user
+// express-session uses cookies which the WiiU does not support during the registration process
+
+// temp, in-memory session storage
+const sessionStore = {};
+
+function sessionMiddlware(request, response, next) {
+ const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
+
+ if (!sessionStore[ip]) {
+ sessionStore[ip] = {};
+ }
+
+ const session = sessionStore[ip];
+
+ request.session = session;
+
+ return next();
+}
+
module.exports = sessionMiddlware;
\ No newline at end of file
diff --git a/src/middleware/xml-parser.js b/src/middleware/xml-parser.js
index 12fb614..5ed1578 100644
--- a/src/middleware/xml-parser.js
+++ b/src/middleware/xml-parser.js
@@ -1,35 +1,35 @@
-const { document: xmlParser } = require('xmlbuilder2');
-
-function XMLMiddleware(request, response, next) {
- if (request.method == 'POST' || request.method == 'PUT') {
- const headers = request.headers;
- let body = '';
-
- if (
- !headers['content-type'] ||
- !headers['content-type'].toLowerCase().includes('xml')
- ) {
- return next();
- }
-
- request.setEncoding('utf-8');
- request.on('data', (chunk) => {
- body += chunk;
- });
-
- request.on('end', () => {
- try {
- request.body = xmlParser(body);
- request.body = request.body.toObject();
- } catch (error) {
- return next();
- }
-
- next();
- });
- } else {
- next();
- }
-}
-
+const { document: xmlParser } = require('xmlbuilder2');
+
+function XMLMiddleware(request, response, next) {
+ if (request.method == 'POST' || request.method == 'PUT') {
+ const headers = request.headers;
+ let body = '';
+
+ if (
+ !headers['content-type'] ||
+ !headers['content-type'].toLowerCase().includes('xml')
+ ) {
+ return next();
+ }
+
+ request.setEncoding('utf-8');
+ request.on('data', (chunk) => {
+ body += chunk;
+ });
+
+ request.on('end', () => {
+ try {
+ request.body = xmlParser(body);
+ request.body = request.body.toObject();
+ } catch (error) {
+ return next();
+ }
+
+ next();
+ });
+ } else {
+ next();
+ }
+}
+
module.exports = XMLMiddleware;
\ No newline at end of file
diff --git a/src/models/device.js b/src/models/device.js
index 4a1c4bd..c04816d 100644
--- a/src/models/device.js
+++ b/src/models/device.js
@@ -1,37 +1,37 @@
-const { Schema, model } = require('mongoose');
-
-const DeviceAttributeSchema = new Schema({
- created_date: String,
- name: String,
- value: String,
-});
-
-const DeviceAttribute = model('DeviceAttribute', DeviceAttributeSchema);
-
-const DeviceSchema = new Schema({
- is_emulator: {
- type: Boolean,
- default: false
- },
- console_type: {
- type: String,
- enum: ['wup', 'ctr', 'spr', 'ftr', 'ktr', 'red', 'jan'] // wup is WiiU, the rest are the 3DS family. Only wup is used atm
- },
- device_id: Number,
- device_type: Number,
- serial: String,
- device_attributes: [DeviceAttributeSchema],
- soap: {
- token: String,
- account_id: Number,
- }
-});
-
-const Device = model('Device', DeviceSchema);
-
-module.exports = {
- DeviceSchema,
- Device,
- DeviceAttributeSchema,
- DeviceAttribute
+const { Schema, model } = require('mongoose');
+
+const DeviceAttributeSchema = new Schema({
+ created_date: String,
+ name: String,
+ value: String,
+});
+
+const DeviceAttribute = model('DeviceAttribute', DeviceAttributeSchema);
+
+const DeviceSchema = new Schema({
+ is_emulator: {
+ type: Boolean,
+ default: false
+ },
+ console_type: {
+ type: String,
+ enum: ['wup', 'ctr', 'spr', 'ftr', 'ktr', 'red', 'jan'] // wup is WiiU, the rest are the 3DS family. Only wup is used atm
+ },
+ device_id: Number,
+ device_type: Number,
+ serial: String,
+ device_attributes: [DeviceAttributeSchema],
+ soap: {
+ token: String,
+ account_id: Number,
+ }
+});
+
+const Device = model('Device', DeviceSchema);
+
+module.exports = {
+ DeviceSchema,
+ Device,
+ DeviceAttributeSchema,
+ DeviceAttribute
};
\ No newline at end of file
diff --git a/src/models/pnid.js b/src/models/pnid.js
index b642553..5834318 100644
--- a/src/models/pnid.js
+++ b/src/models/pnid.js
@@ -1,220 +1,220 @@
-const { Schema, model } = require('mongoose');
-const uniqueValidator = require('mongoose-unique-validator');
-const bcrypt = require('bcrypt');
-const crypto = require('crypto');
-const util = require('../util');
-const { DeviceSchema } = require('./device');
-
-const PNIDSchema = new Schema({
- access_level: {
- type: Number,
- default: 0 // Standard user
- },
- pid: {
- type: Number,
- unique: true
- },
- creation_date: String,
- updated: String,
- username: {
- type: String,
- unique: true,
- minlength: 6,
- maxlength: 16
- },
- usernameLower: {
- type: String,
- unique: true
- },
- password: String,
- birthdate: String,
- gender: String,
- country: String,
- language: String,
- email: {
- address: String,
- primary: Boolean,
- parent: Boolean,
- reachable: Boolean,
- validated: Boolean,
- validated_date: String,
- id: {
- type: Number,
- unique: true
- }
- },
- region: Number,
- timezone: {
- name: String,
- offset: Number
- },
- mii: {
- name: String,
- primary: Boolean,
- data: String,
- id: {
- type: Number,
- unique: true
- },
- hash: {
- type: String,
- unique: true
- },
- image_url: String,
- image_id: {
- type: Number,
- unique: true
- },
- },
- flags: { // not entirely sure what these are used for
- active: Boolean, // Is the account active? Like, not deleted maybe?
- marketing: Boolean,
- off_device: Boolean
- },
- devices: [DeviceSchema],
- nex: {
- password: String,
- token: String
- },
- identification: { // user identification tokens
- email_code: {
- type: String,
- unique: true
- },
- email_token: {
- type: String,
- unique: true
- },
- access_token: {
- value: String,
- ttl: Number
- },
- refresh_token: {
- value: String,
- ttl: Number
- }
- }
-});
-
-PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'});
-
-/*
- According to http://pf2m.com/tools/rank.php Nintendo PID's start at 1,800,000,000 and count down with each account
- This means the max PID is 1799999999 and hard-limits the number of potential accounts to 1,800,000,000
- The author of that site does not give any information on how they found this out, but it does seem to hold true
- https://account.nintendo.net/v1/api/admin/mapped_ids?input_type=pid&output_type=user_id&input=1800000000 returns nothing
- https://account.nintendo.net/v1/api/admin/mapped_ids?input_type=pid&output_type=user_id&input=1799999999 returns `prodtest1`
- and the next few accounts counting down seem to be admin, service and internal test accounts
-*/
-PNIDSchema.methods.generatePID = async function() {
- const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this
- const max = 1799999999;
-
- let pid = Math.floor(Math.random() * (max - min + 1) + min);
-
- const inuse = await PNID.findOne({
- pid
- });
-
- pid = (inuse ? await PNID.generatePID() : pid);
-
- this.set('pid', pid);
-};
-
-PNIDSchema.methods.generateNEXPassword = function() {
- function character() {
- const offset = Math.floor(Math.random() * 62);
- if (offset < 10) return offset;
- if (offset < 36) return String.fromCharCode(offset + 55);
- return String.fromCharCode(offset + 61);
- }
-
- const output = [];
-
- while (output.length < 16) {
- output.push(character());
- }
-
- this.set('nex.password', output.join(''));
-};
-
-PNIDSchema.methods.generateEmailValidationCode = async function() {
- let code = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0
-
- const inuse = await PNID.findOne({
- 'identification.email_code': code
- });
-
- code = (inuse ? await PNID.generateEmailValidationCode() : code);
-
- this.set('identification.email_code', code);
-};
-
-PNIDSchema.methods.generateEmailValidationToken = async function() {
- let token = crypto.randomBytes(32).toString('hex');
-
- const inuse = await PNID.findOne({
- 'identification.email_token': token
- });
-
- token = (inuse ? await PNID.generateEmailValidationToken() : token);
-
- this.set('identification.email_token', token);
-};
-
-PNIDSchema.methods.getDevice = async function(document) {
- const devices = this.get('devices');
-
- return devices.find(device => {
- return (
- (device.device_id === document.device_id) &&
- (device.device_type === document.device_type) &&
- (device.serial === document.serial)
- );
- });
-};
-
-PNIDSchema.methods.addDevice = async function(device) {
- this.devices.push(device);
-
- await this.save();
-};
-
-PNIDSchema.methods.removeDevice = async function(device) {
- this.devices = this.devices.filter(({ _id }) => _id !== device._id);
-
- await this.save();
-};
-
-PNIDSchema.methods.updateMii = async function({name, primary, data}) {
- this.set('mii.name', name);
- this.set('mii.primary', primary === 'Y');
- this.set('mii.data', data);
-
- await this.save();
-};
-
-PNIDSchema.pre('save', async function(next) {
- if (!this.isModified('password')) {
- return next();
- }
-
- this.set('usernameLower', this.get('username').toLowerCase());
- await this.generatePID();
- await this.generateNEXPassword();
- await this.generateEmailValidationCode();
- await this.generateEmailValidationToken();
-
- const primaryHash = util.nintendoPasswordHash(this.get('password'), this.get('pid'));
- const hash = bcrypt.hashSync(primaryHash, 10);
-
- this.set('password', hash);
- next();
-});
-
-const PNID = model('PNID', PNIDSchema);
-
-module.exports = {
- PNIDSchema,
- PNID
+const { Schema, model } = require('mongoose');
+const uniqueValidator = require('mongoose-unique-validator');
+const bcrypt = require('bcrypt');
+const crypto = require('crypto');
+const util = require('../util');
+const { DeviceSchema } = require('./device');
+
+const PNIDSchema = new Schema({
+ access_level: {
+ type: Number,
+ default: 0 // Standard user
+ },
+ pid: {
+ type: Number,
+ unique: true
+ },
+ creation_date: String,
+ updated: String,
+ username: {
+ type: String,
+ unique: true,
+ minlength: 6,
+ maxlength: 16
+ },
+ usernameLower: {
+ type: String,
+ unique: true
+ },
+ password: String,
+ birthdate: String,
+ gender: String,
+ country: String,
+ language: String,
+ email: {
+ address: String,
+ primary: Boolean,
+ parent: Boolean,
+ reachable: Boolean,
+ validated: Boolean,
+ validated_date: String,
+ id: {
+ type: Number,
+ unique: true
+ }
+ },
+ region: Number,
+ timezone: {
+ name: String,
+ offset: Number
+ },
+ mii: {
+ name: String,
+ primary: Boolean,
+ data: String,
+ id: {
+ type: Number,
+ unique: true
+ },
+ hash: {
+ type: String,
+ unique: true
+ },
+ image_url: String,
+ image_id: {
+ type: Number,
+ unique: true
+ },
+ },
+ flags: { // not entirely sure what these are used for
+ active: Boolean, // Is the account active? Like, not deleted maybe?
+ marketing: Boolean,
+ off_device: Boolean
+ },
+ devices: [DeviceSchema],
+ nex: {
+ password: String,
+ token: String
+ },
+ identification: { // user identification tokens
+ email_code: {
+ type: String,
+ unique: true
+ },
+ email_token: {
+ type: String,
+ unique: true
+ },
+ access_token: {
+ value: String,
+ ttl: Number
+ },
+ refresh_token: {
+ value: String,
+ ttl: Number
+ }
+ }
+});
+
+PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'});
+
+/*
+ According to http://pf2m.com/tools/rank.php Nintendo PID's start at 1,800,000,000 and count down with each account
+ This means the max PID is 1799999999 and hard-limits the number of potential accounts to 1,800,000,000
+ The author of that site does not give any information on how they found this out, but it does seem to hold true
+ https://account.nintendo.net/v1/api/admin/mapped_ids?input_type=pid&output_type=user_id&input=1800000000 returns nothing
+ https://account.nintendo.net/v1/api/admin/mapped_ids?input_type=pid&output_type=user_id&input=1799999999 returns `prodtest1`
+ and the next few accounts counting down seem to be admin, service and internal test accounts
+*/
+PNIDSchema.methods.generatePID = async function() {
+ const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this
+ const max = 1799999999;
+
+ let pid = Math.floor(Math.random() * (max - min + 1) + min);
+
+ const inuse = await PNID.findOne({
+ pid
+ });
+
+ pid = (inuse ? await PNID.generatePID() : pid);
+
+ this.set('pid', pid);
+};
+
+PNIDSchema.methods.generateNEXPassword = function() {
+ function character() {
+ const offset = Math.floor(Math.random() * 62);
+ if (offset < 10) return offset;
+ if (offset < 36) return String.fromCharCode(offset + 55);
+ return String.fromCharCode(offset + 61);
+ }
+
+ const output = [];
+
+ while (output.length < 16) {
+ output.push(character());
+ }
+
+ this.set('nex.password', output.join(''));
+};
+
+PNIDSchema.methods.generateEmailValidationCode = async function() {
+ let code = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0
+
+ const inuse = await PNID.findOne({
+ 'identification.email_code': code
+ });
+
+ code = (inuse ? await PNID.generateEmailValidationCode() : code);
+
+ this.set('identification.email_code', code);
+};
+
+PNIDSchema.methods.generateEmailValidationToken = async function() {
+ let token = crypto.randomBytes(32).toString('hex');
+
+ const inuse = await PNID.findOne({
+ 'identification.email_token': token
+ });
+
+ token = (inuse ? await PNID.generateEmailValidationToken() : token);
+
+ this.set('identification.email_token', token);
+};
+
+PNIDSchema.methods.getDevice = async function(document) {
+ const devices = this.get('devices');
+
+ return devices.find(device => {
+ return (
+ (device.device_id === document.device_id) &&
+ (device.device_type === document.device_type) &&
+ (device.serial === document.serial)
+ );
+ });
+};
+
+PNIDSchema.methods.addDevice = async function(device) {
+ this.devices.push(device);
+
+ await this.save();
+};
+
+PNIDSchema.methods.removeDevice = async function(device) {
+ this.devices = this.devices.filter(({ _id }) => _id !== device._id);
+
+ await this.save();
+};
+
+PNIDSchema.methods.updateMii = async function({name, primary, data}) {
+ this.set('mii.name', name);
+ this.set('mii.primary', primary === 'Y');
+ this.set('mii.data', data);
+
+ await this.save();
+};
+
+PNIDSchema.pre('save', async function(next) {
+ if (!this.isModified('password')) {
+ return next();
+ }
+
+ this.set('usernameLower', this.get('username').toLowerCase());
+ await this.generatePID();
+ await this.generateNEXPassword();
+ await this.generateEmailValidationCode();
+ await this.generateEmailValidationToken();
+
+ const primaryHash = util.nintendoPasswordHash(this.get('password'), this.get('pid'));
+ const hash = bcrypt.hashSync(primaryHash, 10);
+
+ this.set('password', hash);
+ next();
+});
+
+const PNID = model('PNID', PNIDSchema);
+
+module.exports = {
+ PNIDSchema,
+ PNID
};
\ No newline at end of file
diff --git a/src/server.js b/src/server.js
index 3ec6a57..ef672dc 100644
--- a/src/server.js
+++ b/src/server.js
@@ -1,59 +1,74 @@
-process.title = 'Pretendo - Account';
-
-const express = require('express');
-const morgan = require('morgan');
-const xmlparser = require('./middleware/xml-parser');
-const database = require('./database');
-const logger = require('../logger');
-const config = require('./config.json');
-
-const { http: { port } } = config;
-const app = express();
-
-const account = require('./services/account');
-
-// START APPLICATION
-app.set('etag', false);
-app.disable('x-powered-by');
-
-// Create router
-logger.info('Setting up Middleware');
-app.use(morgan('dev'));
-app.use(express.json());
-app.use(express.urlencoded({
- extended: true
-}));
-app.use(xmlparser);
-
-// import the servers into one
-app.use(account);
-
-// 404 handler
-logger.info('Creating 404 status handler');
-app.use((request, response) => {
- response.status(404);
- response.send();
-});
-
-// non-404 error handler
-logger.info('Creating non-404 status handler');
-app.use((error, request, response) => {
- const status = error.status || 500;
-
- response.status(status);
-
- response.json({
- app: 'api',
- status,
- error: error.message
- });
-});
-
-// Starts the server
-logger.info('Starting server');
-
-database.connect().then(() => {
- app.listen(port, () => {
- logger.success(`Server started on port ${port}`);
- });
+process.title = 'Pretendo - Account';
+
+const express = require('express');
+const morgan = require('morgan');
+const xmlparser = require('./middleware/xml-parser');
+const database = require('./database');
+const util = require('./util');
+const logger = require('../logger');
+const config = require('./config.json');
+
+const { http: { port } } = config;
+const app = express();
+
+const accountWiiU = require('./services/wiiu');
+//const account3DS = require('./services/3ds');
+
+// START APPLICATION
+app.set('etag', false);
+app.disable('x-powered-by');
+
+// Create router
+logger.info('Setting up Middleware');
+app.use(morgan('dev'));
+app.use(express.json());
+app.use(express.urlencoded({
+ extended: true
+}));
+app.use(xmlparser);
+
+// import the servers into one
+app.use(accountWiiU);
+//app.use(account3DS);
+
+// 404 handler
+logger.info('Creating 404 status handler');
+app.use((request, response) => {
+ const fullUrl = util.fullUrl(request);
+ const deviceId = request.headers['X-Nintendo-Device-ID'] || 'Unknown';
+
+ logger.warn(`HTTP 404 at ${fullUrl} from ${deviceId}`);
+
+ response.status(404);
+ response.json({
+ app: 'api',
+ status: 404,
+ error: 'Route not found'
+ });
+});
+
+// non-404 error handler
+logger.info('Creating non-404 status handler');
+app.use((error, request, response) => {
+ const status = error.status || 500;
+ const fullUrl = util.fullUrl(request);
+ const deviceId = request.headers['X-Nintendo-Device-ID'] || 'Unknown';
+
+ logger.warn(`HTTP ${status} at ${fullUrl} from ${deviceId}: ${error.message}`);
+
+ response.status(status);
+ response.json({
+ app: 'api',
+ status,
+ error: error.message
+ });
+});
+
+// Starts the server
+logger.info('Starting server');
+
+database.connect().then(() => {
+ app.listen(port, () => {
+ logger.success(`Server started on port ${port}`);
+ });
});
\ No newline at end of file
diff --git a/src/services/3ds/index.js b/src/services/3ds/index.js
new file mode 100644
index 0000000..43134f1
--- /dev/null
+++ b/src/services/3ds/index.js
@@ -0,0 +1,22 @@
+// handles NASC endpoints
+
+const express = require('express');
+const subdomain = require('express-subdomain');
+const logger = require('../../../logger');
+const routes = require('./routes');
+
+// Main router for endpoints
+const router = express.Router();
+
+// Router to handle the subdomain restriction
+const nasc = express.Router();
+
+// Create subdomains
+logger.info('[ACCOUNT - 3DS] Creating \'nasc\' subdomain');
+router.use(subdomain('nasc', nasc));
+
+// Setup routes
+logger.info('[ACCOUNT - 3DS] Applying imported routes');
+nasc.use('/ac', routes.ACCOUNT);
+
+module.exports = router;
\ No newline at end of file
diff --git a/src/services/3ds/routes/account.js b/src/services/3ds/routes/account.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/services/3ds/routes/index.js b/src/services/3ds/routes/index.js
new file mode 100644
index 0000000..669919e
--- /dev/null
+++ b/src/services/3ds/routes/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ACCOUNT: require('./account')
+};
\ No newline at end of file
diff --git a/src/services/account/index.js b/src/services/wiiu/index.js
similarity index 75%
rename from src/services/account/index.js
rename to src/services/wiiu/index.js
index 635c68b..e75ae4e 100644
--- a/src/services/account/index.js
+++ b/src/services/wiiu/index.js
@@ -1,32 +1,35 @@
-const express = require('express');
-const subdomain = require('express-subdomain');
-const sessionMiddleware = require('../../middleware/session');
-const pnidMiddleware = require('../../middleware/pnid');
-const logger = require('../../../logger');
-const routes = require('./routes');
-
-// Main router for endpoints
-const router = express.Router();
-
-// Router to handle the subdomain restriction
-const account = express.Router();
-
-// Create subdomains
-logger.info('[ACCOUNT] Creating \'account\' subdomain');
-router.use(subdomain('account', account));
-
-logger.info('[ACCOUNT] Importing middleware');
-account.use(sessionMiddleware);
-account.use(pnidMiddleware);
-
-// Setup routes
-logger.info('[ACCOUNT] Applying imported routes');
-account.use('/v1/api/admin', routes.ADMIN);
-account.use('/v1/api/content', routes.CONTENT);
-account.use('/v1/api/devices', routes.DEVICES);
-account.use('/v1/api/oauth20', routes.OAUTH);
-account.use('/v1/api/people', routes.PEOPLE);
-account.use('/v1/api/provider', routes.PROVIDER);
-account.use('/v1/api/support', routes.SUPPORT);
-
+// handles "account.nintendo.net" endpoints
+
+const express = require('express');
+const subdomain = require('express-subdomain');
+const sessionMiddleware = require('../../middleware/session');
+const pnidMiddleware = require('../../middleware/pnid');
+const logger = require('../../../logger');
+const routes = require('./routes');
+
+// Router to handle the subdomain restriction
+const account = express.Router();
+
+logger.info('[ACCOUNT - WiiU] Importing middleware');
+account.use(sessionMiddleware);
+account.use(pnidMiddleware);
+
+// Setup routes
+logger.info('[ACCOUNT - WiiU] Applying imported routes');
+account.use('/v1/api/admin', routes.ADMIN);
+account.use('/v1/api/content', routes.CONTENT);
+account.use('/v1/api/devices', routes.DEVICES);
+account.use('/v1/api/miis', routes.MIIS);
+account.use('/v1/api/oauth20', routes.OAUTH);
+account.use('/v1/api/people', routes.PEOPLE);
+account.use('/v1/api/provider', routes.PROVIDER);
+account.use('/v1/api/support', routes.SUPPORT);
+
+// Main router for endpoints
+const router = express.Router();
+
+// Create subdomains
+logger.info('[ACCOUNT - WiiU] Creating \'account\' subdomain');
+router.use(subdomain('account', account));
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/admin.js b/src/services/wiiu/routes/admin.js
similarity index 96%
rename from src/services/account/routes/admin.js
rename to src/services/wiiu/routes/admin.js
index 118602b..12ea600 100644
--- a/src/services/account/routes/admin.js
+++ b/src/services/wiiu/routes/admin.js
@@ -1,39 +1,39 @@
-const router = require('express').Router();
-const xmlbuilder = require('xmlbuilder');
-const { PNID } = require('../../../models/pnid');
-const clientHeaderCheck = require('../../../middleware/client-header');
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/admin/mapped_ids
- * Description: Maps given input to expected output
- */
-router.get('/mapped_ids', clientHeaderCheck, async (request, response) => {
-
- let { input: inputList, input_type: inputType, output_type: outputType } = request.query;
- inputList = inputList.split(',');
-
- if (inputType === 'user_id') {
- inputType = 'usernameLower';
-
- inputList = inputList.map(name => name.toLowerCase());
- }
-
- if (outputType === 'user_id') {
- outputType = 'username';
- }
-
- let results = await PNID.where(inputType, inputList);
- results = results.map(user => ({
- in_id: user.get(inputType),
- out_id: user.get(outputType) || ''
- }));
-
- response.send(xmlbuilder.create({
- mapped_ids: {
- mapped_id: results
- }
- }).end());
-});
-
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const { PNID } = require('../../../models/pnid');
+const clientHeaderCheck = require('../../../middleware/client-header');
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/admin/mapped_ids
+ * Description: Maps given input to expected output
+ */
+router.get('/mapped_ids', clientHeaderCheck, async (request, response) => {
+
+ let { input: inputList, input_type: inputType, output_type: outputType } = request.query;
+ inputList = inputList.split(',');
+
+ if (inputType === 'user_id') {
+ inputType = 'usernameLower';
+
+ inputList = inputList.map(name => name.toLowerCase());
+ }
+
+ if (outputType === 'user_id') {
+ outputType = 'username';
+ }
+
+ let results = await PNID.where(inputType, inputList);
+ results = results.map(user => ({
+ in_id: user.get(inputType),
+ out_id: user.get(outputType) || ''
+ }));
+
+ response.send(xmlbuilder.create({
+ mapped_ids: {
+ mapped_id: results
+ }
+ }).end());
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/content.js b/src/services/wiiu/routes/content.js
similarity index 96%
rename from src/services/account/routes/content.js
rename to src/services/wiiu/routes/content.js
index 1a5c052..394a793 100644
--- a/src/services/account/routes/content.js
+++ b/src/services/wiiu/routes/content.js
@@ -1,161 +1,161 @@
-const router = require('express').Router();
-const xmlbuilder = require('xmlbuilder');
-const timezones = require('../timezones.json');
-const clientHeaderCheck = require('../../../middleware/client-header');
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/content/agreements/TYPE/REGION/VERSION
- * Description: Sends the client requested agreement
- */
-router.get('/agreements/:type/:region/:version', clientHeaderCheck, (request, response) => {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', new Date().getTime());
-
- // Registration process has started
- request.session.registration_status = 0;
-
- response.send(xmlbuilder.create({
- agreements: {
- agreement: [
- {
- country: 'US',
- language: 'en',
- language_name: 'English',
- publish_date: '2014-09-29T20:07:35',
- texts: {
- '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
- '@xsi:type': 'chunkedStoredAgreementText',
-
- main_title: {
- '#cdata': 'Pretendo Network Services Agreement'
- },
- agree_text: {
- '#cdata': 'I Accept'
- },
- non_agree_text: {
- '#cdata': 'I Decline'
- },
- main_text: {
- '@index': '1',
- '#cdata': 'Dont be dumb'
- }
- },
- type: 'NINTENDO-NETWORK-EULA',
- version: '0300',
- },
- {
- country: 'US',
- language: 'en',
- language_name: 'Español',
- publish_date: '2014-09-29T20:07:35',
- texts: {
- '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
- '@xsi:type': 'chunkedStoredAgreementText',
-
- main_title: {
- '#cdata': 'Pretendo Network Services Agreement'
- },
- agree_text: {
- '#cdata': 'I Accept'
- },
- non_agree_text: {
- '#cdata': 'I Decline'
- },
- main_text: {
- '@index': '1',
- '#cdata': 'Dont be dumb'
- }
- },
- type: 'NINTENDO-NETWORK-EULA',
- version: '0300',
- },
- {
- country: 'US',
- language: 'en',
- language_name: 'Français',
- publish_date: '2014-09-29T20:07:35',
- texts: {
- '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
- '@xsi:type': 'chunkedStoredAgreementText',
-
- main_title: {
- '#cdata': 'Pretendo Network Services Agreement'
- },
- agree_text: {
- '#cdata': 'I Accept'
- },
- non_agree_text: {
- '#cdata': 'I Decline'
- },
- main_text: {
- '@index': '1',
- '#cdata': 'Dont be dumb'
- }
- },
- type: 'NINTENDO-NETWORK-EULA',
- version: '0300',
- }
- ]
- }
- }).end());
-});
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/content/time_zones/COUNTRY/LANGUAGE
- * Description: Sends the client the requested timezones
- */
-router.get('/time_zones/:countryCode/:language', clientHeaderCheck, (request, response) => {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', new Date().getTime());
-
- // Status should be 0 from previous request in registration process
- if (request.session.registration_status !== 0) {
- response.status(400);
-
- return response.send(xmlbuilder.create({
- error: {
- cause: 'Bad Request',
- code: '1600',
- message: 'Unable to process request'
- }
- }).end());
- }
-
- /*
- // Old method. Crashes WiiU when sending a list with over 32 entries, but otherwise works
- // countryTimezones is "countries-and-timezones" module
-
- const country = countryTimezones.getCountry(countryCode);
- const timezones = country.timezones.map((timezone, index) => {
- const data = countryTimezones.getTimezone(timezone);
-
- return {
- area: data.name,
- language,
- name: data.name,
- utc_offset: data.utcOffset * 6 * 10,
- order: index+1
- };
- });
- */
-
- const { countryCode, language } = request.params;
-
- const regionLanguages = timezones[countryCode];
- const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0];
-
- // Bump status to allow access to next endpoint
- request.session.registration_status = 1;
-
- response.send(xmlbuilder.create({
- timezones: {
- timezone: regionTimezones
- }
- }).end());
-});
-
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const timezones = require('../timezones.json');
+const clientHeaderCheck = require('../../../middleware/client-header');
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/content/agreements/TYPE/REGION/VERSION
+ * Description: Sends the client requested agreement
+ */
+router.get('/agreements/:type/:region/:version', clientHeaderCheck, (request, response) => {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', new Date().getTime());
+
+ // Registration process has started
+ request.session.registration_status = 0;
+
+ response.send(xmlbuilder.create({
+ agreements: {
+ agreement: [
+ {
+ country: 'US',
+ language: 'en',
+ language_name: 'English',
+ publish_date: '2014-09-29T20:07:35',
+ texts: {
+ '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
+ '@xsi:type': 'chunkedStoredAgreementText',
+
+ main_title: {
+ '#cdata': 'Pretendo Network Services Agreement'
+ },
+ agree_text: {
+ '#cdata': 'I Accept'
+ },
+ non_agree_text: {
+ '#cdata': 'I Decline'
+ },
+ main_text: {
+ '@index': '1',
+ '#cdata': 'Dont be dumb'
+ }
+ },
+ type: 'NINTENDO-NETWORK-EULA',
+ version: '0300',
+ },
+ {
+ country: 'US',
+ language: 'en',
+ language_name: 'Español',
+ publish_date: '2014-09-29T20:07:35',
+ texts: {
+ '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
+ '@xsi:type': 'chunkedStoredAgreementText',
+
+ main_title: {
+ '#cdata': 'Pretendo Network Services Agreement'
+ },
+ agree_text: {
+ '#cdata': 'I Accept'
+ },
+ non_agree_text: {
+ '#cdata': 'I Decline'
+ },
+ main_text: {
+ '@index': '1',
+ '#cdata': 'Dont be dumb'
+ }
+ },
+ type: 'NINTENDO-NETWORK-EULA',
+ version: '0300',
+ },
+ {
+ country: 'US',
+ language: 'en',
+ language_name: 'Français',
+ publish_date: '2014-09-29T20:07:35',
+ texts: {
+ '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
+ '@xsi:type': 'chunkedStoredAgreementText',
+
+ main_title: {
+ '#cdata': 'Pretendo Network Services Agreement'
+ },
+ agree_text: {
+ '#cdata': 'I Accept'
+ },
+ non_agree_text: {
+ '#cdata': 'I Decline'
+ },
+ main_text: {
+ '@index': '1',
+ '#cdata': 'Dont be dumb'
+ }
+ },
+ type: 'NINTENDO-NETWORK-EULA',
+ version: '0300',
+ }
+ ]
+ }
+ }).end());
+});
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/content/time_zones/COUNTRY/LANGUAGE
+ * Description: Sends the client the requested timezones
+ */
+router.get('/time_zones/:countryCode/:language', clientHeaderCheck, (request, response) => {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', new Date().getTime());
+
+ // Status should be 0 from previous request in registration process
+ if (request.session.registration_status !== 0) {
+ response.status(400);
+
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'Bad Request',
+ code: '1600',
+ message: 'Unable to process request'
+ }
+ }).end());
+ }
+
+ /*
+ // Old method. Crashes WiiU when sending a list with over 32 entries, but otherwise works
+ // countryTimezones is "countries-and-timezones" module
+
+ const country = countryTimezones.getCountry(countryCode);
+ const timezones = country.timezones.map((timezone, index) => {
+ const data = countryTimezones.getTimezone(timezone);
+
+ return {
+ area: data.name,
+ language,
+ name: data.name,
+ utc_offset: data.utcOffset * 6 * 10,
+ order: index+1
+ };
+ });
+ */
+
+ const { countryCode, language } = request.params;
+
+ const regionLanguages = timezones[countryCode];
+ const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0];
+
+ // Bump status to allow access to next endpoint
+ request.session.registration_status = 1;
+
+ response.send(xmlbuilder.create({
+ timezones: {
+ timezone: regionTimezones
+ }
+ }).end());
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/devices.js b/src/services/wiiu/routes/devices.js
similarity index 96%
rename from src/services/account/routes/devices.js
rename to src/services/wiiu/routes/devices.js
index d666937..3c7c398 100644
--- a/src/services/account/routes/devices.js
+++ b/src/services/wiiu/routes/devices.js
@@ -1,16 +1,16 @@
-const router = require('express').Router();
-const xmlbuilder = require('xmlbuilder');
-const clientHeaderCheck = require('../../../middleware/client-header');
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/devices/@current/status
- * Description: Unknown use
- */
-router.get('/@current/status', clientHeaderCheck, async (request, response) => {
- response.send(xmlbuilder.create({
- device: ''
- }).end());
-});
-
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const clientHeaderCheck = require('../../../middleware/client-header');
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/devices/@current/status
+ * Description: Unknown use
+ */
+router.get('/@current/status', clientHeaderCheck, async (request, response) => {
+ response.send(xmlbuilder.create({
+ device: ''
+ }).end());
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/index.js b/src/services/wiiu/routes/index.js
similarity index 87%
rename from src/services/account/routes/index.js
rename to src/services/wiiu/routes/index.js
index 32493af..b5becf4 100644
--- a/src/services/account/routes/index.js
+++ b/src/services/wiiu/routes/index.js
@@ -1,9 +1,10 @@
-module.exports = {
- ADMIN: require('./admin'),
- CONTENT: require('./content'),
- DEVICES: require('./devices'),
- OAUTH: require('./oauth'),
- PEOPLE: require('./people'),
- PROVIDER: require('./provider'),
- SUPPORT: require('./support'),
+module.exports = {
+ ADMIN: require('./admin'),
+ CONTENT: require('./content'),
+ DEVICES: require('./devices'),
+ MIIS: require('./miis'),
+ OAUTH: require('./oauth'),
+ PEOPLE: require('./people'),
+ PROVIDER: require('./provider'),
+ SUPPORT: require('./support'),
};
\ No newline at end of file
diff --git a/src/services/wiiu/routes/miis.js b/src/services/wiiu/routes/miis.js
new file mode 100644
index 0000000..5297875
--- /dev/null
+++ b/src/services/wiiu/routes/miis.js
@@ -0,0 +1,95 @@
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const { PNID } = require('../../../models/pnid');
+const clientHeaderCheck = require('../../../middleware/client-header');
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/miis
+ * Description: Returns a list of NNID miis
+ */
+router.get('/', clientHeaderCheck, async (request, response) => {
+
+ const { pids } = request.query;
+
+ const results = await PNID.where('pid', pids);
+ const miis = [];
+
+ // We don't have a Mii renderer yet so hard code the images
+ const hardCodedImages = [
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_standard.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_standard.png',
+ type: 'standard'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_frustrated_face.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_frustrated_face.png',
+ type: 'frustrated_face'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_happy_face.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_happy_face.png',
+ type: 'happy_face'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_like_face.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_like_face.png',
+ type: 'like_face'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_normal_face.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_normal_face.png',
+ type: 'normal_face'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_puzzled_face.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_puzzled_face.png',
+ type: 'puzzled_face'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_surprised_face.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_surprised_face.png',
+ type: 'surprised_face'
+ },
+ {
+ cached_url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_whole_body.png',
+ id: 1292086302,
+ url: 'http://mii-images.cdn.nintendo.net/zcz5c1xf62bb_whole_body.png',
+ type: 'whole_body'
+ }
+ ];
+
+ for (const user of results) {
+ const { mii } = user;
+
+ miis.push({
+ data: mii.data.replace(/(\r\n|\n|\r)/gm, ''),
+ id: mii.id,
+ images: {
+ image: hardCodedImages
+ },
+ name: mii.name,
+ pid: user.pid,
+ primary: mii.primary ? 'Y' : 'N',
+ user_id: user.username
+ });
+ }
+
+ //console.log(results[0].mii.data.replace(/(\r\n|\n|\r)/gm, ''));
+
+ response.send(xmlbuilder.create({
+ miis: {
+ mii: miis
+ }
+ }).end());
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/oauth.js b/src/services/wiiu/routes/oauth.js
similarity index 96%
rename from src/services/account/routes/oauth.js
rename to src/services/wiiu/routes/oauth.js
index 79e3321..55f6506 100644
--- a/src/services/account/routes/oauth.js
+++ b/src/services/wiiu/routes/oauth.js
@@ -1,116 +1,116 @@
-const router = require('express').Router();
-const xmlbuilder = require('xmlbuilder');
-const bcrypt = require('bcrypt');
-const fs = require('fs-extra');
-const clientHeaderCheck = require('../../../middleware/client-header');
-const database = require('../../../database');
-const util = require('../../../util');
-
-/**
- * [POST]
- * Replacement for: https://account.nintendo.net/v1/api/oauth20/access_token/generate
- * Description: Generates an access token for a user
- */
-router.post('/access_token/generate', clientHeaderCheck, async (request, response) => {
- const titleId = request.headers['x-nintendo-title-id'];
- const { body } = request;
- const { grant_type, user_id, password } = body;
-
- if (!['password', 'refresh_token'].includes(grant_type)) {
- response.status(400);
- return response.send(xmlbuilder.create({
- error: {
- cause: 'grant_type',
- code: '0004',
- message: 'Invalid Grant Type'
- }
- }).end());
- }
-
- if (!user_id || user_id.trim() === '') {
- response.status(400);
- return response.send(xmlbuilder.create({
- error: {
- cause: 'user_id',
- code: '0002',
- message: 'user_id format is invalid'
- }
- }).end());
- }
-
- if (!password || password.trim() === '') {
- response.status(400);
- return response.send(xmlbuilder.create({
- error: {
- cause: 'password',
- code: '0002',
- message: 'password format is invalid'
- }
- }).end());
- }
-
- const pnid = await database.getUserByUsername(user_id);
-
- if (!pnid || !bcrypt.compareSync(password, pnid.password)) {
- response.status(400);
- return response.send(xmlbuilder.create({
- error: {
- code: '0106',
- message: 'Invalid account ID or password'
- }
- }).end());
- }
-
- const cryptoPath = `${__dirname}/../../../../certs/access`;
-
- if (!fs.pathExistsSync(cryptoPath)) {
- // Need to generate keys
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '0000',
- message: 'Could not find access key crypto path'
- }
- }
- }).end());
- }
-
- const publicKey = fs.readFileSync(`${cryptoPath}/public.pem`);
- const hmacSecret = fs.readFileSync(`${cryptoPath}/secret.key`);
-
- const cryptoOptions = {
- public_key: publicKey,
- hmac_secret: hmacSecret
- };
-
- const accessTokenOptions = {
- system_type: 0x1, // WiiU
- token_type: 0x1, // OAuth Access,
- pid: pnid.get('pid'),
- title_id: BigInt(parseInt(titleId, 16)),
- date: BigInt(Date.now())
- };
-
- const refreshTokenOptions = {
- system_type: 0x1, // WiiU
- token_type: 0x2, // OAuth Refresh,
- pid: pnid.get('pid'),
- title_id: BigInt(parseInt(titleId, 16)),
- date: BigInt(Date.now())
- };
-
- const accessToken = util.generateToken(cryptoOptions, accessTokenOptions);
- const refreshToken = util.generateToken(cryptoOptions, refreshTokenOptions);
-
- response.send(xmlbuilder.create({
- OAuth20: {
- access_token: {
- token: accessToken,
- refresh_token: refreshToken,
- expires_in: 3600
- }
- }
- }).end());
-});
-
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const bcrypt = require('bcrypt');
+const fs = require('fs-extra');
+const clientHeaderCheck = require('../../../middleware/client-header');
+const database = require('../../../database');
+const util = require('../../../util');
+
+/**
+ * [POST]
+ * Replacement for: https://account.nintendo.net/v1/api/oauth20/access_token/generate
+ * Description: Generates an access token for a user
+ */
+router.post('/access_token/generate', clientHeaderCheck, async (request, response) => {
+ const titleId = request.headers['x-nintendo-title-id'];
+ const { body } = request;
+ const { grant_type, user_id, password } = body;
+
+ if (!['password', 'refresh_token'].includes(grant_type)) {
+ response.status(400);
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'grant_type',
+ code: '0004',
+ message: 'Invalid Grant Type'
+ }
+ }).end());
+ }
+
+ if (!user_id || user_id.trim() === '') {
+ response.status(400);
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'user_id',
+ code: '0002',
+ message: 'user_id format is invalid'
+ }
+ }).end());
+ }
+
+ if (!password || password.trim() === '') {
+ response.status(400);
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'password',
+ code: '0002',
+ message: 'password format is invalid'
+ }
+ }).end());
+ }
+
+ const pnid = await database.getUserByUsername(user_id);
+
+ if (!pnid || !bcrypt.compareSync(password, pnid.password)) {
+ response.status(400);
+ return response.send(xmlbuilder.create({
+ error: {
+ code: '0106',
+ message: 'Invalid account ID or password'
+ }
+ }).end());
+ }
+
+ const cryptoPath = `${__dirname}/../../../../certs/access`;
+
+ if (!fs.pathExistsSync(cryptoPath)) {
+ // Need to generate keys
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '0000',
+ message: 'Could not find access key crypto path'
+ }
+ }
+ }).end());
+ }
+
+ const publicKey = fs.readFileSync(`${cryptoPath}/public.pem`);
+ const hmacSecret = fs.readFileSync(`${cryptoPath}/secret.key`);
+
+ const cryptoOptions = {
+ public_key: publicKey,
+ hmac_secret: hmacSecret
+ };
+
+ const accessTokenOptions = {
+ system_type: 0x1, // WiiU
+ token_type: 0x1, // OAuth Access,
+ pid: pnid.get('pid'),
+ title_id: BigInt(parseInt(titleId, 16)),
+ date: BigInt(Date.now())
+ };
+
+ const refreshTokenOptions = {
+ system_type: 0x1, // WiiU
+ token_type: 0x2, // OAuth Refresh,
+ pid: pnid.get('pid'),
+ title_id: BigInt(parseInt(titleId, 16)),
+ date: BigInt(Date.now())
+ };
+
+ const accessToken = util.generateToken(cryptoOptions, accessTokenOptions);
+ const refreshToken = util.generateToken(cryptoOptions, refreshTokenOptions);
+
+ response.send(xmlbuilder.create({
+ OAuth20: {
+ access_token: {
+ token: accessToken,
+ refresh_token: refreshToken,
+ expires_in: 3600
+ }
+ }
+ }).end());
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/people.js b/src/services/wiiu/routes/people.js
similarity index 97%
rename from src/services/account/routes/people.js
rename to src/services/wiiu/routes/people.js
index 7673962..3fffab5 100644
--- a/src/services/account/routes/people.js
+++ b/src/services/wiiu/routes/people.js
@@ -1,283 +1,283 @@
-const router = require('express').Router();
-const xmlbuilder = require('xmlbuilder');
-const moment = require('moment');
-const crypto = require('crypto');
-const { PNID } = require('../../../models/pnid');
-const clientHeaderCheck = require('../../../middleware/client-header');
-const database = require('../../../database');
-const mailer = require('../../../mailer');
-require('moment-timezone');
-
-router.get('/:username', clientHeaderCheck, async (request, response) => {
- // Status should be 1 from previous request in registration process
- if (request.session.registration_status !== 1) {
- response.status(400);
-
- return response.send(xmlbuilder.create({
- error: {
- cause: 'Bad Request',
- code: '1600',
- message: 'Unable to process request'
- }
- }).end());
- }
-
- const { username } = request.params;
-
- const userExists = await database.doesUserExist(username);
-
- if (userExists) {
- response.status(400);
-
- return response.end(xmlbuilder.create({
- errors: {
- error: {
- code: '0100',
- message: 'Account ID already exists'
- }
- }
- }).end());
- }
-
- // Bump status to allow access to next endpoint
- request.session.registration_status = 2;
-
- response.status(200);
- response.end();
-});
-
-router.post('/', clientHeaderCheck, async (request, response) => {
- // Status should be 3 from previous request in registration process
- if (request.session.registration_status !== 3) {
- response.status(400);
-
- return response.send(xmlbuilder.create({
- error: {
- cause: 'Bad Request',
- code: '1600',
- message: 'Unable to process request'
- }
- }).end());
- }
-
- const person = request.body.get('person');
-
- const userExists = await database.doesUserExist(person.get('user_id'));
-
- if (userExists) {
- response.status(400);
-
- return response.end(xmlbuilder.create({
- errors: {
- error: {
- code: '0100',
- message: 'Account ID already exists'
- }
- }
- }).end());
- }
-
- const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS');
-
- const document = {
- pid: 1, // will be overwritten before saving
- creation_date: creationDate,
- updated: creationDate,
- username: person.get('user_id'),
- password: person.get('password'), // will be hashed before saving
- birthdate: person.get('birth_date'),
- gender: person.get('gender'),
- country: person.get('country'),
- language: person.get('language'),
- email: {
- address: person.get('email').get('address'),
- primary: person.get('email').get('primary') === 'Y',
- parent: person.get('email').get('parent') === 'Y',
- reachable: false,
- validated: person.get('email').get('validated') === 'Y',
- id: Math.floor(Math.random(10000000000)*10000000000)
- },
- region: person.get('region'),
- timezone: {
- name: person.get('tz_name'),
- offset: (moment.tz(person.get('tz_name')).utcOffset() * 60)
- },
- mii: {
- name: person.get('mii').get('name'),
- primary: person.get('mii').get('name') === 'Y',
- data: person.get('mii').get('data'),
- id: Math.floor(Math.random(10000000000)*10000000000),
- hash: crypto.createHash('md5').update(person.get('mii').get('data')).digest('hex'),
- image_url: 'https://mii-secure.account.nintendo.net/2rtgf01lztoqo_standard.tga',
- image_id: Math.floor(Math.random(10000000000)*10000000000)
- },
- flags: {
- active: true,
- marketing: person.get('marketing_flag') === 'Y',
- off_device: person.get('off_device_flag') === 'Y'
- },
- validation: {
- email_code: 1, // will be overwritten before saving
- email_token: '' // will be overwritten before saving
- }
- };
-
- const newUser = new PNID(document);
-
- newUser.save()
- .catch(error => {
- console.log(error);
-
- response.status(400);
-
- return response.send(xmlbuilder.create({
- error: {
- cause: 'Bad Request',
- code: '1600',
- message: 'Unable to process request'
- }
- }).end());
- })
- .then(async newUser => {
- await mailer.send(
- newUser.get('email'),
- '[Prentendo Network] Please confirm your e-mail address',
- `Hello,
-
- Your Prentendo Network ID activation is almost complete. Please click the link below to confirm your e-mail address and complete the activation process.
-
- https://account.pretendo.cc/account/email-confirmation?token=` + newUser.get('identification.email_token') + `
-
- If you are unable to connect to the above URL, please enter the following confirmation code on the device to which your Prentendo Network ID is linked.
-
- <<Confirmation code: ` + newUser.get('identification.email_code') + '>>'
- );
-
- delete request.session.registration_status;
-
- response.send(xmlbuilder.create({
- person: {
- pid: newUser.get('pid')
- }
- }).end());
- });
-});
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/people/@me/profile
- * Description: Gets a users profile
- */
-router.get('/@me/profile', clientHeaderCheck, async (request, response) => {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', new Date().getTime());
-
-
- const { pnid } = request;
-
- const person = await database.getUserProfileJSONByPID(pnid.get('pid'));
-
- response.send(xmlbuilder.create({
- person
- }).end());
-
-
- //response.send(`84955611ctr_initial_device_account_idUSER19583364384955489environmentUSERPRODESHOP.NINTENDO.NETINTERNAL327329101Y1998-09-22US2017-12-29T04:11:242019-11-24T15:26:06persistent_id800000432019-11-24T15:26:06transferable_id_base1200000444b6221d2019-11-24T15:26:06transferable_id_base_common1180000444b6221d2019-11-24T15:26:06uuid_account42ef6c46-0ea3-11ea-97fe-010144b6221d2019-11-24T15:26:06uuid_common3d9b06d8-0ea3-11ea-97fe-010144b6221dMen2019-06-02T04:17:56YY1750087940halolink44@gmail.com50463196NYYDEFAULTINTERNAL WSY2017-12-29T04:12:32COMPLETEDAwBzMOlVognnx0GCk6r2p0D0B2n+cgAA0lJSAGUAZABEAHUAYwBrAHMAAAAAAGQrAAAWAQJoRBgmNEYUgRIXaI0AiiWBSUhQUgBlAGQARAB1AGMAawBzAHMAAAAAAJZY1151699634u2jg043u028xhttps://mii-secure.account.nintendo.net/u2jg043u028x_standard.tga1319591505https://mii-secure.account.nintendo.net/u2jg043u028x_standard.tgastandardRedDucksY822870016America/New_YorkRedDuckss-18000`);
-
-});
-
-/**
- * [POST]
- * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices
- * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile
- */
-router.post('/@me/devices', clientHeaderCheck, async (request, response) => {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', new Date().getTime());
-
- // We don't care about the device attributes
- // The console ignores them and PNIDs are not tied to consoles anyway
- // So the server also ignores them and does not save the ones posted here
-
- const { pnid } = request;
-
- const person = await database.getUserProfileJSONByPID(pnid.get('pid'));
-
- response.send(xmlbuilder.create({
- person
- }).end());
-});
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices
- * Description: Returns only user devices
- */
-router.get('/@me/devices', clientHeaderCheck, async (request, response) => {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', new Date().getTime());
-
- const { pnid, headers } = request;
-
- response.send(xmlbuilder.create({
- devices: [
- {
- device: {
- device_id: headers['x-nintendo-device-id'],
- language: headers['accept-language'],
- updated: moment().format('YYYY-MM-DDTHH:MM:SS'),
- pid: pnid.get('pid'),
- platform_id: headers['x-nintendo-platform-id'],
- region: headers['x-nintendo-region'],
- serial_number: headers['x-nintendo-serial-number'],
- status: 'ACTIVE',
- system_version: headers['x-nintendo-system-version'],
- type: 'RETAIL',
- updated_by: 'USER'
- }
- }
- ]
- }).end());
-});
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/owner
- * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile
- */
-router.get('/@me/devices/owner', clientHeaderCheck, async (request, response) => {
- response.set('Content-Type', 'text/xml');
- response.set('Server', 'Nintendo 3DS (http)');
- response.set('X-Nintendo-Date', moment().add(5, 'h'));
-
- const { pnid } = request;
-
- const person = await database.getUserProfileJSONByPID(pnid.get('pid'));
-
- response.send(xmlbuilder.create({
- person
- }).end());
-});
-
-
-/**
- * [PUT]
- * Replacement for: https://account.nintendo.net/v1/api/people/@me/miis/@primary
- * Description: Updates a users Mii
- */
-router.put('/@me/miis/@primary', clientHeaderCheck, async (request, response) => {
- const { pnid } = request;
-
- const mii = request.body.get('mii');
-
- const [name, primary, data] = [mii.get('name'), mii.get('primary'), mii.get('data')]
-
- await pnid.updateMii({name, primary, data});
-
- response.send('');
-});
-
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const moment = require('moment');
+const crypto = require('crypto');
+const { PNID } = require('../../../models/pnid');
+const clientHeaderCheck = require('../../../middleware/client-header');
+const database = require('../../../database');
+const mailer = require('../../../mailer');
+require('moment-timezone');
+
+router.get('/:username', clientHeaderCheck, async (request, response) => {
+ // Status should be 1 from previous request in registration process
+ if (request.session.registration_status !== 1) {
+ response.status(400);
+
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'Bad Request',
+ code: '1600',
+ message: 'Unable to process request'
+ }
+ }).end());
+ }
+
+ const { username } = request.params;
+
+ const userExists = await database.doesUserExist(username);
+
+ if (userExists) {
+ response.status(400);
+
+ return response.end(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '0100',
+ message: 'Account ID already exists'
+ }
+ }
+ }).end());
+ }
+
+ // Bump status to allow access to next endpoint
+ request.session.registration_status = 2;
+
+ response.status(200);
+ response.end();
+});
+
+router.post('/', clientHeaderCheck, async (request, response) => {
+ // Status should be 3 from previous request in registration process
+ if (request.session.registration_status !== 3) {
+ response.status(400);
+
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'Bad Request',
+ code: '1600',
+ message: 'Unable to process request'
+ }
+ }).end());
+ }
+
+ const person = request.body.get('person');
+
+ const userExists = await database.doesUserExist(person.get('user_id'));
+
+ if (userExists) {
+ response.status(400);
+
+ return response.end(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '0100',
+ message: 'Account ID already exists'
+ }
+ }
+ }).end());
+ }
+
+ const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS');
+
+ const document = {
+ pid: 1, // will be overwritten before saving
+ creation_date: creationDate,
+ updated: creationDate,
+ username: person.get('user_id'),
+ password: person.get('password'), // will be hashed before saving
+ birthdate: person.get('birth_date'),
+ gender: person.get('gender'),
+ country: person.get('country'),
+ language: person.get('language'),
+ email: {
+ address: person.get('email').get('address'),
+ primary: person.get('email').get('primary') === 'Y',
+ parent: person.get('email').get('parent') === 'Y',
+ reachable: false,
+ validated: person.get('email').get('validated') === 'Y',
+ id: Math.floor(Math.random(10000000000)*10000000000)
+ },
+ region: person.get('region'),
+ timezone: {
+ name: person.get('tz_name'),
+ offset: (moment.tz(person.get('tz_name')).utcOffset() * 60)
+ },
+ mii: {
+ name: person.get('mii').get('name'),
+ primary: person.get('mii').get('name') === 'Y',
+ data: person.get('mii').get('data'),
+ id: Math.floor(Math.random(10000000000)*10000000000),
+ hash: crypto.createHash('md5').update(person.get('mii').get('data')).digest('hex'),
+ image_url: 'https://mii-secure.account.nintendo.net/2rtgf01lztoqo_standard.tga',
+ image_id: Math.floor(Math.random(10000000000)*10000000000)
+ },
+ flags: {
+ active: true,
+ marketing: person.get('marketing_flag') === 'Y',
+ off_device: person.get('off_device_flag') === 'Y'
+ },
+ validation: {
+ email_code: 1, // will be overwritten before saving
+ email_token: '' // will be overwritten before saving
+ }
+ };
+
+ const newUser = new PNID(document);
+
+ newUser.save()
+ .catch(error => {
+ console.log(error);
+
+ response.status(400);
+
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'Bad Request',
+ code: '1600',
+ message: 'Unable to process request'
+ }
+ }).end());
+ })
+ .then(async newUser => {
+ await mailer.send(
+ newUser.get('email'),
+ '[Prentendo Network] Please confirm your e-mail address',
+ `Hello,
+
+ Your Prentendo Network ID activation is almost complete. Please click the link below to confirm your e-mail address and complete the activation process.
+
+ https://account.pretendo.cc/account/email-confirmation?token=` + newUser.get('identification.email_token') + `
+
+ If you are unable to connect to the above URL, please enter the following confirmation code on the device to which your Prentendo Network ID is linked.
+
+ <<Confirmation code: ` + newUser.get('identification.email_code') + '>>'
+ );
+
+ delete request.session.registration_status;
+
+ response.send(xmlbuilder.create({
+ person: {
+ pid: newUser.get('pid')
+ }
+ }).end());
+ });
+});
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/people/@me/profile
+ * Description: Gets a users profile
+ */
+router.get('/@me/profile', clientHeaderCheck, async (request, response) => {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', new Date().getTime());
+
+
+ const { pnid } = request;
+
+ const person = await database.getUserProfileJSONByPID(pnid.get('pid'));
+
+ response.send(xmlbuilder.create({
+ person
+ }).end());
+
+
+ //response.send(`84955611ctr_initial_device_account_idUSER19583364384955489environmentUSERPRODESHOP.NINTENDO.NETINTERNAL327329101Y1998-09-22US2017-12-29T04:11:242019-11-24T15:26:06persistent_id800000432019-11-24T15:26:06transferable_id_base1200000444b6221d2019-11-24T15:26:06transferable_id_base_common1180000444b6221d2019-11-24T15:26:06uuid_account42ef6c46-0ea3-11ea-97fe-010144b6221d2019-11-24T15:26:06uuid_common3d9b06d8-0ea3-11ea-97fe-010144b6221dMen2019-06-02T04:17:56YY1750087940halolink44@gmail.com50463196NYYDEFAULTINTERNAL WSY2017-12-29T04:12:32COMPLETEDAwBzMOlVognnx0GCk6r2p0D0B2n+cgAA0lJSAGUAZABEAHUAYwBrAHMAAAAAAGQrAAAWAQJoRBgmNEYUgRIXaI0AiiWBSUhQUgBlAGQARAB1AGMAawBzAHMAAAAAAJZY1151699634u2jg043u028xhttps://mii-secure.account.nintendo.net/u2jg043u028x_standard.tga1319591505https://mii-secure.account.nintendo.net/u2jg043u028x_standard.tgastandardRedDucksY822870016America/New_YorkRedDuckss-18000`);
+
+});
+
+/**
+ * [POST]
+ * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices
+ * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile
+ */
+router.post('/@me/devices', clientHeaderCheck, async (request, response) => {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', new Date().getTime());
+
+ // We don't care about the device attributes
+ // The console ignores them and PNIDs are not tied to consoles anyway
+ // So the server also ignores them and does not save the ones posted here
+
+ const { pnid } = request;
+
+ const person = await database.getUserProfileJSONByPID(pnid.get('pid'));
+
+ response.send(xmlbuilder.create({
+ person
+ }).end());
+});
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices
+ * Description: Returns only user devices
+ */
+router.get('/@me/devices', clientHeaderCheck, async (request, response) => {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', new Date().getTime());
+
+ const { pnid, headers } = request;
+
+ response.send(xmlbuilder.create({
+ devices: [
+ {
+ device: {
+ device_id: headers['x-nintendo-device-id'],
+ language: headers['accept-language'],
+ updated: moment().format('YYYY-MM-DDTHH:MM:SS'),
+ pid: pnid.get('pid'),
+ platform_id: headers['x-nintendo-platform-id'],
+ region: headers['x-nintendo-region'],
+ serial_number: headers['x-nintendo-serial-number'],
+ status: 'ACTIVE',
+ system_version: headers['x-nintendo-system-version'],
+ type: 'RETAIL',
+ updated_by: 'USER'
+ }
+ }
+ ]
+ }).end());
+});
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/owner
+ * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile
+ */
+router.get('/@me/devices/owner', clientHeaderCheck, async (request, response) => {
+ response.set('Content-Type', 'text/xml');
+ response.set('Server', 'Nintendo 3DS (http)');
+ response.set('X-Nintendo-Date', moment().add(5, 'h'));
+
+ const { pnid } = request;
+
+ const person = await database.getUserProfileJSONByPID(pnid.get('pid'));
+
+ response.send(xmlbuilder.create({
+ person
+ }).end());
+});
+
+
+/**
+ * [PUT]
+ * Replacement for: https://account.nintendo.net/v1/api/people/@me/miis/@primary
+ * Description: Updates a users Mii
+ */
+router.put('/@me/miis/@primary', clientHeaderCheck, async (request, response) => {
+ const { pnid } = request;
+
+ const mii = request.body.get('mii');
+
+ const [name, primary, data] = [mii.get('name'), mii.get('primary'), mii.get('data')]
+
+ await pnid.updateMii({name, primary, data});
+
+ response.send('');
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/provider.js b/src/services/wiiu/routes/provider.js
similarity index 93%
rename from src/services/account/routes/provider.js
rename to src/services/wiiu/routes/provider.js
index 5f902cb..c55eb5d 100644
--- a/src/services/account/routes/provider.js
+++ b/src/services/wiiu/routes/provider.js
@@ -1,149 +1,151 @@
-const router = require('express').Router();
-const xmlbuilder = require('xmlbuilder');
-const fs = require('fs-extra');
-const util = require('../../../util');
-const servers = require('../../../servers.json');
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/provider/service_token/@me
- * Description: Gets a service token
- */
-router.get('/service_token/@me', async (request, response) => {
- const { pnid } = request;
-
- const titleId = request.headers['x-nintendo-title-id'];
- const server = servers.find(({ title_ids }) => title_ids.includes(titleId));
-
- if (!server) {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '1021',
- message: 'The requested game server was not found'
- }
- }
- }).end());
- }
-
- const { name, system } = server;
-
- const cryptoPath = `${__dirname}/../../../../certs/nex/${name}`;
-
- if (!fs.pathExistsSync(cryptoPath)) {
- // Need to generate keys
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '1021',
- message: 'The requested game server was not found'
- }
- }
- }).end());
- }
-
- const publicKey = fs.readFileSync(`${cryptoPath}/public.pem`);
- const hmacSecret = fs.readFileSync(`${cryptoPath}/secret.key`);
-
- const cryptoOptions = {
- public_key: publicKey,
- hmac_secret: hmacSecret
- };
-
- const tokenOptions = {
- system_type: system,
- token_type: 0x4, // service token,
- pid: pnid.get('pid'),
- title_id: BigInt(parseInt(titleId, 16)),
- date: BigInt(Date.now())
- };
-
- const serviceToken = util.generateToken(cryptoOptions, tokenOptions);
-
- response.send(xmlbuilder.create({
- service_token: {
- token: serviceToken
- }
- }).end());
-});
-
-/**
- * [GET]
- * Replacement for: https://account.nintendo.net/v1/api/provider/nex_token/@me
- * Description: Gets a NEX server address and token
- */
-router.get('/nex_token/@me', async (request, response) => {
- const { game_server_id: gameServerID } = request.query;
- const { pnid } = request;
-
- if (!gameServerID) {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '0118',
- message: 'Unique ID and Game Server ID are not linked'
- }
- }
- }).end());
- }
-
- const server = servers.find(({ server_id }) => server_id === gameServerID);
-
- if (!server) {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '1021',
- message: 'The requested game server was not found'
- }
- }
- }).end());
- }
-
- const { name, ip, port, system } = server;
- const titleId = request.headers['x-nintendo-title-id'];
-
- const cryptoPath = `${__dirname}/../../../../certs/nex/${name}`;
-
- if (!fs.pathExistsSync(cryptoPath)) {
- // Need to generate keys
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '1021',
- message: 'The requested game server was not found'
- }
- }
- }).end());
- }
-
- const publicKey = fs.readFileSync(`${cryptoPath}/public.pem`);
- const hmacSecret = fs.readFileSync(`${cryptoPath}/secret.key`);
-
- const cryptoOptions = {
- public_key: publicKey,
- hmac_secret: hmacSecret
- };
-
- const tokenOptions = {
- system_type: system,
- token_type: 0x3, // nex token,
- pid: pnid.get('pid'),
- title_id: BigInt(parseInt(titleId, 16)),
- date: BigInt(Date.now())
- };
-
- const nexToken = util.generateToken(cryptoOptions, tokenOptions);
-
- response.send(xmlbuilder.create({
- nex_token: {
- host: ip,
- nex_password: pnid.get('nex.password'),
- pid: pnid.get('pid'),
- port: port,
- token: nexToken
- }
- }).end());
-});
-
+const router = require('express').Router();
+const xmlbuilder = require('xmlbuilder');
+const fs = require('fs-extra');
+const util = require('../../../util');
+const servers = require('../../../servers.json');
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/provider/service_token/@me
+ * Description: Gets a service token
+ */
+router.get('/service_token/@me', async (request, response) => {
+ const { pnid } = request;
+
+ const titleId = request.headers['x-nintendo-title-id'];
+ const server = servers.find(({ title_ids }) => title_ids.includes(titleId));
+
+ console.log(titleId);
+
+ if (!server) {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '1021',
+ message: 'The requested game server was not found'
+ }
+ }
+ }).end());
+ }
+
+ const { name, system } = server;
+
+ const cryptoPath = `${__dirname}/../../../../certs/service/${name}`;
+
+ if (!fs.pathExistsSync(cryptoPath)) {
+ // Need to generate keys
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '1021',
+ message: 'The requested game server was not found'
+ }
+ }
+ }).end());
+ }
+
+ const publicKey = fs.readFileSync(`${cryptoPath}/public.pem`);
+ const hmacSecret = fs.readFileSync(`${cryptoPath}/secret.key`);
+
+ const cryptoOptions = {
+ public_key: publicKey,
+ hmac_secret: hmacSecret
+ };
+
+ const tokenOptions = {
+ system_type: system,
+ token_type: 0x4, // service token,
+ pid: pnid.get('pid'),
+ title_id: BigInt(parseInt(titleId, 16)),
+ date: BigInt(Date.now())
+ };
+
+ const serviceToken = util.generateToken(cryptoOptions, tokenOptions);
+
+ response.send(xmlbuilder.create({
+ service_token: {
+ token: serviceToken
+ }
+ }).end());
+});
+
+/**
+ * [GET]
+ * Replacement for: https://account.nintendo.net/v1/api/provider/nex_token/@me
+ * Description: Gets a NEX server address and token
+ */
+router.get('/nex_token/@me', async (request, response) => {
+ const { game_server_id: gameServerID } = request.query;
+ const { pnid } = request;
+
+ if (!gameServerID) {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '0118',
+ message: 'Unique ID and Game Server ID are not linked'
+ }
+ }
+ }).end());
+ }
+
+ const server = servers.find(({ server_id }) => server_id === gameServerID);
+
+ if (!server) {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '1021',
+ message: 'The requested game server was not found'
+ }
+ }
+ }).end());
+ }
+
+ const { name, ip, port, system } = server;
+ const titleId = request.headers['x-nintendo-title-id'];
+
+ const cryptoPath = `${__dirname}/../../../../certs/nex/${name}`;
+
+ if (!fs.pathExistsSync(cryptoPath)) {
+ // Need to generate keys
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '1021',
+ message: 'The requested game server was not found'
+ }
+ }
+ }).end());
+ }
+
+ const publicKey = fs.readFileSync(`${cryptoPath}/public.pem`);
+ const hmacSecret = fs.readFileSync(`${cryptoPath}/secret.key`);
+
+ const cryptoOptions = {
+ public_key: publicKey,
+ hmac_secret: hmacSecret
+ };
+
+ const tokenOptions = {
+ system_type: system,
+ token_type: 0x3, // nex token,
+ pid: pnid.get('pid'),
+ title_id: BigInt(parseInt(titleId, 16)),
+ date: BigInt(Date.now())
+ };
+
+ const nexToken = util.generateToken(cryptoOptions, tokenOptions);
+
+ response.send(xmlbuilder.create({
+ nex_token: {
+ host: ip,
+ nex_password: pnid.get('nex.password'),
+ pid: pnid.get('pid'),
+ port: port,
+ token: nexToken
+ }
+ }).end());
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/routes/support.js b/src/services/wiiu/routes/support.js
similarity index 95%
rename from src/services/account/routes/support.js
rename to src/services/wiiu/routes/support.js
index 069824d..322d3ba 100644
--- a/src/services/account/routes/support.js
+++ b/src/services/wiiu/routes/support.js
@@ -1,55 +1,55 @@
-const router = require('express').Router();
-const dns = require('dns');
-const xmlbuilder = require('xmlbuilder');
-const clientHeaderCheck = require('../../../middleware/client-header');
-
-router.post('/validate/email', clientHeaderCheck, async (request, response) => {
- // Status should be 2 from previous request in registration process
- if (request.session.registration_status !== 2) {
- response.status(400);
-
- return response.send(xmlbuilder.create({
- error: {
- cause: 'Bad Request',
- code: '1600',
- message: 'Unable to process request'
- }
- }).end());
- }
-
- const { email } = request.body;
-
- if (!email) {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- cause: 'email',
- code: '0103',
- message: 'Email format is invalid'
- }
- }
- }).end());
- }
-
- const domain = email.split('@')[1];
-
- dns.resolveMx(domain, (error) => {
- if (error) {
- return response.send(xmlbuilder.create({
- errors: {
- error: {
- code: '1126',
- message: 'The domain "' + domain + '" is not accessible.'
- }
- }
- }).end());
- }
-
- request.session.registration_status = 3;
-
- response.status(200);
- response.end();
- });
-});
-
+const router = require('express').Router();
+const dns = require('dns');
+const xmlbuilder = require('xmlbuilder');
+const clientHeaderCheck = require('../../../middleware/client-header');
+
+router.post('/validate/email', clientHeaderCheck, async (request, response) => {
+ // Status should be 2 from previous request in registration process
+ if (request.session.registration_status !== 2) {
+ response.status(400);
+
+ return response.send(xmlbuilder.create({
+ error: {
+ cause: 'Bad Request',
+ code: '1600',
+ message: 'Unable to process request'
+ }
+ }).end());
+ }
+
+ const { email } = request.body;
+
+ if (!email) {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ cause: 'email',
+ code: '0103',
+ message: 'Email format is invalid'
+ }
+ }
+ }).end());
+ }
+
+ const domain = email.split('@')[1];
+
+ dns.resolveMx(domain, (error) => {
+ if (error) {
+ return response.send(xmlbuilder.create({
+ errors: {
+ error: {
+ code: '1126',
+ message: 'The domain "' + domain + '" is not accessible.'
+ }
+ }
+ }).end());
+ }
+
+ request.session.registration_status = 3;
+
+ response.status(200);
+ response.end();
+ });
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/src/services/account/timezones.json b/src/services/wiiu/timezones.json
similarity index 100%
rename from src/services/account/timezones.json
rename to src/services/wiiu/timezones.json
diff --git a/src/util.js b/src/util.js
index 2e9596c..f3128b5 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,172 +1,182 @@
-const crypto = require('crypto');
-const NodeRSA = require('node-rsa');
-const fs = require('fs-extra');
-
-function nintendoPasswordHash(password, pid) {
- const pidBuffer = Buffer.alloc(4);
- pidBuffer.writeUInt32LE(pid);
-
- const unpacked = Buffer.concat([
- pidBuffer,
- Buffer.from('\x02\x65\x43\x46'),
- Buffer.from(password)
- ]);
- const hashed = crypto.createHash('sha256').update(unpacked).digest().toString('hex');
-
- return hashed;
-}
-
-function generateRandomInt(length = 4) {
- return Math.floor(Math.pow(10, length-1) + Math.random() * 9 * Math.pow(10, length-1));
-}
-
-function generateToken(cryptoOptions, tokenOptions) {
-
- // Access and refresh tokens use a different format since they must be much smaller
- if ([0x1, 0x2].includes(tokenOptions.token_type)) {
- const cryptoPath = `${__dirname}/../certs/access`;
- const aesKey = Buffer.from(fs.readFileSync(`${cryptoPath}/aes.key`, { encoding: 'utf8' }), 'hex');
- const dataBuffer = Buffer.alloc(4 + 8);
-
- dataBuffer.writeUInt32LE(tokenOptions.pid, 0x0);
- dataBuffer.writeBigUInt64LE(tokenOptions.date, 0x4);
-
- const iv = Buffer.alloc(16);
- const cipher = crypto.createCipheriv('aes-128-cbc', aesKey, iv);
-
- let encryptedBody = cipher.update(dataBuffer);
- encryptedBody = Buffer.concat([encryptedBody, cipher.final()]);
-
- return encryptedBody.toString('base64');
- }
-
- const publicKey = new NodeRSA(cryptoOptions.public_key, 'pkcs8-public-pem', {
- environment: 'browser',
- encryptionScheme: {
- 'hash': 'sha256',
- }
- });
-
- // Create the buffer containing the token data
- const dataBuffer = Buffer.alloc(1 + 1 + 4 + 8 + 8);
-
- dataBuffer.writeUInt8(tokenOptions.system_type, 0x0);
- dataBuffer.writeUInt8(tokenOptions.token_type, 0x1);
- dataBuffer.writeUInt32LE(tokenOptions.pid, 0x2);
- dataBuffer.writeBigUInt64LE(tokenOptions.title_id, 0x6);
- dataBuffer.writeBigUInt64LE(tokenOptions.date, 0xE);
-
- // Calculate the signature of the token body
- const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(dataBuffer);
- const signature = hmac.digest();
-
- // Generate random AES key and IV
- const key = crypto.randomBytes(16);
- const iv = crypto.randomBytes(16);
-
- // Encrypt the token body with AES
- const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
-
- let encryptedBody = cipher.update(dataBuffer);
- encryptedBody = Buffer.concat([encryptedBody, cipher.final()]);
-
- // Encrypt the AES key with RSA public key
- const encryptedKey = publicKey.encrypt(key);
-
- // Create crypto config token section
- const cryptoConfig = Buffer.concat([
- encryptedKey,
- iv
- ]);
-
- // Build the token
- const token = Buffer.concat([
- cryptoConfig,
- signature,
- encryptedBody
- ]);
-
- return token.toString('base64'); // Encode to base64 for transport
-}
-
-function decryptToken(token) {
-
- // Access and refresh tokens use a different format since they must be much smaller
- // Assume a small length means access or refresh token
- if (token.length <= 32) {
- const cryptoPath = `${__dirname}/../certs/access`;
- const aesKey = Buffer.from(fs.readFileSync(`${cryptoPath}/aes.key`, { encoding: 'utf8' }), 'hex');
-
- const iv = Buffer.alloc(16);
-
- const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv);
-
- let decryptedBody = decipher.update(token);
- decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
-
- return decryptedBody;
- }
-
- const cryptoPath = `${__dirname}/../certs/access`;
-
- const cryptoOptions = {
- private_key: fs.readFileSync(`${cryptoPath}/private.pem`),
- hmac_secret: fs.readFileSync(`${cryptoPath}/secret.key`)
- };
-
- const privateKey = new NodeRSA(cryptoOptions.private_key, 'pkcs1-private-pem', {
- environment: 'browser',
- encryptionScheme: {
- 'hash': 'sha256',
- }
- });
-
- const cryptoConfig = token.subarray(0, 0x90);
- const signature = token.subarray(0x90, 0xA4);
- const encryptedBody = token.subarray(0xA4);
-
- const encryptedAESKey = cryptoConfig.subarray(0, 128);
- const iv = cryptoConfig.subarray(128);
-
- const decryptedAESKey = privateKey.decrypt(encryptedAESKey);
-
- const decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv);
-
- let decryptedBody = decipher.update(encryptedBody);
- decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
-
- const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(decryptedBody);
- const calculatedSignature = hmac.digest();
-
- if (calculatedSignature !== signature) {
- console.log('Token signature did not match');
- return null;
- }
-
- return decryptedBody;
-}
-
-function unpackToken(token) {
- if (token.length <= 32) {
- return {
- pid: token.readUInt32LE(0x0),
- date: token.readBigUInt64LE(0x2)
- };
- }
-
- return {
- system_type: token.readUInt8(0x0),
- token_type: token.readUInt8(0x1),
- pid: token.readUInt32LE(0x2),
- title_id: token.readBigUInt64LE(0x6),
- date: token.readBigUInt64LE(0xE)
- };
-}
-
-module.exports = {
- nintendoPasswordHash,
- generateRandomInt,
- generateToken,
- decryptToken,
- unpackToken
+const crypto = require('crypto');
+const NodeRSA = require('node-rsa');
+const fs = require('fs-extra');
+const url = require('url');
+
+function nintendoPasswordHash(password, pid) {
+ const pidBuffer = Buffer.alloc(4);
+ pidBuffer.writeUInt32LE(pid);
+
+ const unpacked = Buffer.concat([
+ pidBuffer,
+ Buffer.from('\x02\x65\x43\x46'),
+ Buffer.from(password)
+ ]);
+ const hashed = crypto.createHash('sha256').update(unpacked).digest().toString('hex');
+
+ return hashed;
+}
+
+function generateRandomInt(length = 4) {
+ return Math.floor(Math.pow(10, length-1) + Math.random() * 9 * Math.pow(10, length-1));
+}
+
+function generateToken(cryptoOptions, tokenOptions) {
+
+ // Access and refresh tokens use a different format since they must be much smaller
+ if ([0x1, 0x2].includes(tokenOptions.token_type)) {
+ const cryptoPath = `${__dirname}/../certs/access`;
+ const aesKey = Buffer.from(fs.readFileSync(`${cryptoPath}/aes.key`, { encoding: 'utf8' }), 'hex');
+ const dataBuffer = Buffer.alloc(4 + 8);
+
+ dataBuffer.writeUInt32LE(tokenOptions.pid, 0x0);
+ dataBuffer.writeBigUInt64LE(tokenOptions.date, 0x4);
+
+ const iv = Buffer.alloc(16);
+ const cipher = crypto.createCipheriv('aes-128-cbc', aesKey, iv);
+
+ let encryptedBody = cipher.update(dataBuffer);
+ encryptedBody = Buffer.concat([encryptedBody, cipher.final()]);
+
+ return encryptedBody.toString('base64');
+ }
+
+ const publicKey = new NodeRSA(cryptoOptions.public_key, 'pkcs8-public-pem', {
+ environment: 'browser',
+ encryptionScheme: {
+ 'hash': 'sha256',
+ }
+ });
+
+ // Create the buffer containing the token data
+ const dataBuffer = Buffer.alloc(1 + 1 + 4 + 8 + 8);
+
+ dataBuffer.writeUInt8(tokenOptions.system_type, 0x0);
+ dataBuffer.writeUInt8(tokenOptions.token_type, 0x1);
+ dataBuffer.writeUInt32LE(tokenOptions.pid, 0x2);
+ dataBuffer.writeBigUInt64LE(tokenOptions.title_id, 0x6);
+ dataBuffer.writeBigUInt64LE(tokenOptions.date, 0xE);
+
+ // Calculate the signature of the token body
+ const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(dataBuffer);
+ const signature = hmac.digest();
+
+ // Generate random AES key and IV
+ const key = crypto.randomBytes(16);
+ const iv = crypto.randomBytes(16);
+
+ // Encrypt the token body with AES
+ const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
+
+ let encryptedBody = cipher.update(dataBuffer);
+ encryptedBody = Buffer.concat([encryptedBody, cipher.final()]);
+
+ // Encrypt the AES key with RSA public key
+ const encryptedKey = publicKey.encrypt(key);
+
+ // Create crypto config token section
+ const cryptoConfig = Buffer.concat([
+ encryptedKey,
+ iv
+ ]);
+
+ // Build the token
+ const token = Buffer.concat([
+ cryptoConfig,
+ signature,
+ encryptedBody
+ ]);
+
+ return token.toString('base64'); // Encode to base64 for transport
+}
+
+function decryptToken(token) {
+
+ // Access and refresh tokens use a different format since they must be much smaller
+ // Assume a small length means access or refresh token
+ if (token.length <= 32) {
+ const cryptoPath = `${__dirname}/../certs/access`;
+ const aesKey = Buffer.from(fs.readFileSync(`${cryptoPath}/aes.key`, { encoding: 'utf8' }), 'hex');
+
+ const iv = Buffer.alloc(16);
+
+ const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv);
+
+ let decryptedBody = decipher.update(token);
+ decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
+
+ return decryptedBody;
+ }
+
+ const cryptoPath = `${__dirname}/../certs/access`;
+
+ const cryptoOptions = {
+ private_key: fs.readFileSync(`${cryptoPath}/private.pem`),
+ hmac_secret: fs.readFileSync(`${cryptoPath}/secret.key`)
+ };
+
+ const privateKey = new NodeRSA(cryptoOptions.private_key, 'pkcs1-private-pem', {
+ environment: 'browser',
+ encryptionScheme: {
+ 'hash': 'sha256',
+ }
+ });
+
+ const cryptoConfig = token.subarray(0, 0x90);
+ const signature = token.subarray(0x90, 0xA4);
+ const encryptedBody = token.subarray(0xA4);
+
+ const encryptedAESKey = cryptoConfig.subarray(0, 128);
+ const iv = cryptoConfig.subarray(128);
+
+ const decryptedAESKey = privateKey.decrypt(encryptedAESKey);
+
+ const decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv);
+
+ let decryptedBody = decipher.update(encryptedBody);
+ decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
+
+ const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(decryptedBody);
+ const calculatedSignature = hmac.digest();
+
+ if (calculatedSignature !== signature) {
+ console.log('Token signature did not match');
+ return null;
+ }
+
+ return decryptedBody;
+}
+
+function unpackToken(token) {
+ if (token.length <= 32) {
+ return {
+ pid: token.readUInt32LE(0x0),
+ date: token.readBigUInt64LE(0x2)
+ };
+ }
+
+ return {
+ system_type: token.readUInt8(0x0),
+ token_type: token.readUInt8(0x1),
+ pid: token.readUInt32LE(0x2),
+ title_id: token.readBigUInt64LE(0x6),
+ date: token.readBigUInt64LE(0xE)
+ };
+}
+
+function fullUrl(request) {
+ return url.format({
+ protocol: request.protocol,
+ host: request.get('host'),
+ pathname: request.originalUrl
+ });
+}
+
+module.exports = {
+ nintendoPasswordHash,
+ generateRandomInt,
+ generateToken,
+ decryptToken,
+ unpackToken,
+ fullUrl
};
\ No newline at end of file