mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-28 03:25:42 -05:00
Merge pull request #123 from PretendoNetwork/cleanup
This commit is contained in:
@@ -11,11 +11,7 @@
|
||||
"discord": {
|
||||
"client_id": "client_id",
|
||||
"client_secret": "client_secret",
|
||||
"guild_id": "Guild ID",
|
||||
"bot_token": "token",
|
||||
"tester_roles": [
|
||||
"role id"
|
||||
]
|
||||
"bot_token": "token"
|
||||
},
|
||||
"stripe": {
|
||||
"secret_key": "sk_secret",
|
||||
@@ -34,6 +30,5 @@
|
||||
"gmail": {
|
||||
"user": "email@gmail.com",
|
||||
"pass": "app-password"
|
||||
},
|
||||
"aes_key": "hex key here"
|
||||
}
|
||||
}
|
||||
117
package-lock.json
generated
117
package-lock.json
generated
@@ -9,10 +9,12 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.5.0",
|
||||
"adm-zip": "^0.5.9",
|
||||
"browserify": "^17.0.0",
|
||||
"colors": "^1.4.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"discord-api-types": "^0.36.1",
|
||||
"discord-oauth2": "github:ryanblenis/discord-oauth2",
|
||||
"express": "^4.17.1",
|
||||
"express-handlebars": "^5.3.1",
|
||||
@@ -136,6 +138,35 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/collection": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz",
|
||||
"integrity": "sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA==",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.5.0.tgz",
|
||||
"integrity": "sha512-S4E1YNz1UxgUfMPpMeqzPPkCfXE877zOsvKM5WEmwIhcpz1PQV7lzqlEOuz194UuwOJLLjQFBgQELnQfCX9UfA==",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^0.7.0",
|
||||
"@sapphire/async-queue": "^1.3.1",
|
||||
"@sapphire/snowflake": "^3.2.2",
|
||||
"discord-api-types": "^0.33.3",
|
||||
"tslib": "^2.4.0",
|
||||
"undici": "^5.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest/node_modules/discord-api-types": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz",
|
||||
"integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg=="
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
|
||||
@@ -222,6 +253,24 @@
|
||||
"integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sapphire/async-queue": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.1.tgz",
|
||||
"integrity": "sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g==",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/snowflake": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.2.tgz",
|
||||
"integrity": "sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz",
|
||||
@@ -1255,6 +1304,11 @@
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.36.1",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.1.tgz",
|
||||
"integrity": "sha512-PTDinUU574hXA9Ko9wrftL1iww1raNiRVKjuPIWQ5Li1g7vQPArpZWw9x01kh/IXLPdzSAJ6H8T0eAYzxzFzIg=="
|
||||
},
|
||||
"node_modules/discord-oauth2": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "git+ssh://git@github.com/ryanblenis/discord-oauth2.git#ca0093d5b34efdd6ec2ce45a4a2678b3282cea31",
|
||||
@@ -4116,6 +4170,11 @@
|
||||
"node": ">= 0.10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/tty-browserify": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
|
||||
@@ -4214,6 +4273,14 @@
|
||||
"undeclared-identifiers": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.6.1.tgz",
|
||||
"integrity": "sha512-yYVqywdCbNb99f/w045wqmv++WExXDjY0FEvLSB7QUZZH6izxrVkF4dJn1aimcvN0+WAhv75Gg7v6VJoqmRtJQ==",
|
||||
"engines": {
|
||||
"node": ">=12.18"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
@@ -4519,6 +4586,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@discordjs/collection": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz",
|
||||
"integrity": "sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA=="
|
||||
},
|
||||
"@discordjs/rest": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.5.0.tgz",
|
||||
"integrity": "sha512-S4E1YNz1UxgUfMPpMeqzPPkCfXE877zOsvKM5WEmwIhcpz1PQV7lzqlEOuz194UuwOJLLjQFBgQELnQfCX9UfA==",
|
||||
"requires": {
|
||||
"@discordjs/collection": "^0.7.0",
|
||||
"@sapphire/async-queue": "^1.3.1",
|
||||
"@sapphire/snowflake": "^3.2.2",
|
||||
"discord-api-types": "^0.33.3",
|
||||
"tslib": "^2.4.0",
|
||||
"undici": "^5.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord-api-types": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz",
|
||||
"integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
|
||||
@@ -4587,6 +4679,16 @@
|
||||
"integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
|
||||
"dev": true
|
||||
},
|
||||
"@sapphire/async-queue": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.1.tgz",
|
||||
"integrity": "sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g=="
|
||||
},
|
||||
"@sapphire/snowflake": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.2.tgz",
|
||||
"integrity": "sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ=="
|
||||
},
|
||||
"@sindresorhus/is": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz",
|
||||
@@ -5439,6 +5541,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"discord-api-types": {
|
||||
"version": "0.36.1",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.1.tgz",
|
||||
"integrity": "sha512-PTDinUU574hXA9Ko9wrftL1iww1raNiRVKjuPIWQ5Li1g7vQPArpZWw9x01kh/IXLPdzSAJ6H8T0eAYzxzFzIg=="
|
||||
},
|
||||
"discord-oauth2": {
|
||||
"version": "git+ssh://git@github.com/ryanblenis/discord-oauth2.git#ca0093d5b34efdd6ec2ce45a4a2678b3282cea31",
|
||||
"integrity": "sha512-J3w3djV6I4PtnLp3lZga4m41DPjqXtyETV8rMG9FSIev5i5mxpkrsfXRni3Jc+qYxH86LcLa7zUwnWHHXpcu0g==",
|
||||
@@ -7632,6 +7739,11 @@
|
||||
"restler": "~3.3.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"tty-browserify": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
|
||||
@@ -7703,6 +7815,11 @@
|
||||
"xtend": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"undici": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.6.1.tgz",
|
||||
"integrity": "sha512-yYVqywdCbNb99f/w045wqmv++WExXDjY0FEvLSB7QUZZH6izxrVkF4dJn1aimcvN0+WAhv75Gg7v6VJoqmRtJQ=="
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
},
|
||||
"homepage": "https://github.com/PretendoNetwork/website#readme",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.5.0",
|
||||
"adm-zip": "^0.5.9",
|
||||
"browserify": "^17.0.0",
|
||||
"colors": "^1.4.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"discord-api-types": "^0.36.1",
|
||||
"discord-oauth2": "github:ryanblenis/discord-oauth2",
|
||||
"express": "^4.17.1",
|
||||
"express-handlebars": "^5.3.1",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Removing until it's done */
|
||||
.setting-card a.edit,
|
||||
.sign-in-history a {
|
||||
@@ -249,6 +253,99 @@ fieldset {
|
||||
background: #a9375b;
|
||||
}
|
||||
|
||||
div.online-files-modal-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
div.online-files-modal-wrapper.hidden {
|
||||
display: none;
|
||||
}
|
||||
div.online-files-modal {
|
||||
background: #393b5f;
|
||||
padding: 48px;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
width: min(660px, 90%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.online-files-modal h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
p.online-files-modal-caption {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
p.online-files-modal-caption span {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.online-files-modal-button-wrapper {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.online-files-modal-button-wrapper button {
|
||||
margin-left: 12px;
|
||||
width: fit-content;
|
||||
}
|
||||
.online-files-modal-button-wrapper button.cancel {
|
||||
background: none;
|
||||
}
|
||||
.online-files-modal-button-wrapper button.confirm {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
height: fit-content;
|
||||
|
||||
background: var(--btn);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
color: var(--text);
|
||||
width: 100%;
|
||||
|
||||
transition: filter 300ms;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
background-color: black; /*change me*/
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
color: var(--text);
|
||||
width: calc(100% - 24px);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
background-color: #4b5595;
|
||||
outline: none;
|
||||
transition: 150ms;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
width: 1600px;
|
||||
height: 1400px;
|
||||
}
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.back-arrow {
|
||||
position: absolute;
|
||||
@@ -261,7 +264,7 @@ form button.unsubscribe:hover {
|
||||
|
||||
div.unsub-modal-wrapper,
|
||||
div.switch-tier-modal-wrapper {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
@@ -19,4 +19,43 @@ document.getElementById('remove-discord-connection')?.addEventListener('click',
|
||||
}
|
||||
})
|
||||
.catch(console.log);
|
||||
});
|
||||
|
||||
const onlineFilesModal = document.querySelector('.online-files-modal-wrapper');
|
||||
const onlineFilesModalButtonConfirm = document.getElementById('onlineFilesConfirmButton');
|
||||
const onlineFilesModalButtonClose = document.getElementById('onlineFilesCloseButton');
|
||||
const onlineFilesModalPasswordInput = document.getElementById('password');
|
||||
|
||||
document.getElementById('download-cemu-files')?.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
|
||||
onlineFilesModal.classList.remove('hidden');
|
||||
});
|
||||
|
||||
onlineFilesModalButtonConfirm?.addEventListener('click', () => {
|
||||
fetch('/account/online-files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: onlineFilesModalPasswordInput.value
|
||||
})
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => URL.createObjectURL(blob))
|
||||
.then(blobUrl => {
|
||||
const a = document.createElement('a');
|
||||
a.href = blobUrl;
|
||||
a.setAttribute('download', 'Cemu Pretendo Online Files.zip');
|
||||
a.click();
|
||||
|
||||
onlineFilesModal.classList.add('hidden');
|
||||
})
|
||||
.catch(console.log);
|
||||
});
|
||||
|
||||
onlineFilesModalButtonClose?.addEventListener('click', () => {
|
||||
onlineFilesModal.classList.add('hidden');
|
||||
});
|
||||
@@ -70,6 +70,7 @@ buttons.submit.addEventListener('click', function(e) {
|
||||
oldTierNameSpan.innerText = currentTierElement.dataset.tierName;
|
||||
newTierNameSpan.innerText = document.querySelector('input[name="tier"]:checked').dataset.tierName;
|
||||
|
||||
document.body.classList.add('modal-open');
|
||||
document.querySelector('.switch-tier-modal-wrapper').classList.remove('hidden');
|
||||
} else {
|
||||
submitForm();
|
||||
@@ -83,12 +84,14 @@ buttons.unsubModal.show.addEventListener('click', function(e) {
|
||||
tierNameSpan.innerText = currentTierElement.dataset.tierName;
|
||||
|
||||
// Show the unsubscribe modal
|
||||
document.body.classList.add('modal-open');
|
||||
document.querySelector('.unsub-modal-wrapper').classList.remove('hidden');
|
||||
});
|
||||
buttons.unsubModal.close.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Hide the unsubscribe modal
|
||||
document.body.classList.remove('modal-open');
|
||||
document.querySelector('.unsub-modal-wrapper').classList.add('hidden');
|
||||
});
|
||||
buttons.unsubModal.confirm.addEventListener('click', function(e) {
|
||||
@@ -101,6 +104,7 @@ buttons.switchTierModal.close.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Hide the switch tier modal
|
||||
document.body.classList.remove('modal-open');
|
||||
document.querySelector('.switch-tier-modal-wrapper').classList.add('hidden');
|
||||
});
|
||||
buttons.switchTierModal.confirm.addEventListener('click', function(e) {
|
||||
|
||||
13
src/middleware/redirect.js
Normal file
13
src/middleware/redirect.js
Normal file
@@ -0,0 +1,13 @@
|
||||
async function redirectMiddleware(request, response, next) {
|
||||
if (request.method === 'POST') {
|
||||
request.redirect = request.body.redirect?.startsWith('/') ? request.body.redirect : null;
|
||||
}
|
||||
|
||||
if (request.query.redirect) {
|
||||
response.locals.redirect = request.query.redirect?.startsWith('/') ? request.query.redirect : null;
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = redirectMiddleware;
|
||||
43
src/middleware/render-data.js
Normal file
43
src/middleware/render-data.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const util = require('../util');
|
||||
const database = require('../database');
|
||||
|
||||
async function renderDataMiddleware(request, response, next) {
|
||||
if (request.path.startsWith('/assets')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Get user local
|
||||
const reqLocale = request.locale;
|
||||
const locale = util.getLocale(reqLocale.region, reqLocale.language);
|
||||
|
||||
response.locals.locale = locale;
|
||||
response.locals.localeString = reqLocale.toString();
|
||||
|
||||
// Get message cookies
|
||||
response.locals.success_message = request.cookies.success_message;
|
||||
response.locals.error_message = request.cookies.error_message;
|
||||
|
||||
// Reset message cookies
|
||||
response.clearCookie('success_message', { domain: '.pretendo.network' });
|
||||
response.clearCookie('error_message', { domain: '.pretendo.network' });
|
||||
|
||||
response.locals.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token;
|
||||
|
||||
if (response.locals.isLoggedIn) {
|
||||
try {
|
||||
response.locals.account = await util.getUserAccountData(request, response);
|
||||
|
||||
request.pnid = await database.PNID.findOne({ pid: response.locals.account.pid });
|
||||
request.account = response.locals.account;
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = renderDataMiddleware;
|
||||
10
src/middleware/require-login.js
Normal file
10
src/middleware/require-login.js
Normal file
@@ -0,0 +1,10 @@
|
||||
async function requireLoginMiddleware(request, response, next) {
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token) {
|
||||
return response.redirect(`/account/login?redirect=${request.originalUrl}`);
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = requireLoginMiddleware;
|
||||
@@ -1,823 +0,0 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const DiscordOauth2 = require('discord-oauth2');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const AdmZip = require('adm-zip');
|
||||
const Stripe = require('stripe');
|
||||
const database = require('../database');
|
||||
const cache = require('../cache');
|
||||
const util = require('../util');
|
||||
const logger = require('../logger');
|
||||
const config = require('../../config.json');
|
||||
|
||||
const { Router } = express;
|
||||
const aesKey = Buffer.from(config.aes_key, 'hex');
|
||||
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
const router = new Router();
|
||||
|
||||
// Create OAuth client
|
||||
const discordOAuth = new DiscordOauth2({
|
||||
clientId: config.discord.client_id,
|
||||
clientSecret: config.discord.client_secret,
|
||||
redirectUri: `${config.http.base_url}/account/connect/discord`,
|
||||
version: 'v9'
|
||||
});
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
// Setup the data to be sent to the handlebars renderer
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
success: request.cookies.success,
|
||||
error: request.cookies.error
|
||||
};
|
||||
|
||||
// Reset message cookies
|
||||
response.clearCookie('success', { domain: '.pretendo.network' });
|
||||
response.clearCookie('error', { domain: '.pretendo.network' });
|
||||
|
||||
// Check for Stripe messages
|
||||
const { upgrade_success } = request.query;
|
||||
|
||||
if (upgrade_success === 'true') {
|
||||
renderData.success = 'Account upgraded successfully';
|
||||
} else if (upgrade_success === 'false') {
|
||||
renderData.error = 'Account upgrade failed';
|
||||
}
|
||||
|
||||
// Attempt to get user data
|
||||
let apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain : '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain : '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain : '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
}
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
// Set user account info to render data
|
||||
const account = apiResponse.body;
|
||||
const pid = account.pid;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
renderData.tierName = pnid.get('connections.stripe.tier_name');
|
||||
renderData.tierLevel = pnid.get('connections.stripe.tier_level');
|
||||
renderData.account = account;
|
||||
renderData.isTester = account.access_level > 0;
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
// Check if a Discord account is linked to the PNID
|
||||
if (account.connections.discord.id && account.connections.discord.id.trim() !== '') {
|
||||
// If Discord account is linked, then get user info
|
||||
try {
|
||||
renderData.discordUser = await discordOAuth.getUser(account.connections.discord.access_token);
|
||||
} catch (error) {
|
||||
// Assume expired, refresh and retry Discord request
|
||||
let tokens;
|
||||
try {
|
||||
tokens = await discordOAuth.tokenRequest({
|
||||
scope: 'identify guilds',
|
||||
grantType: 'refresh_token',
|
||||
refreshToken: account.connections.discord.refresh_token,
|
||||
});
|
||||
} catch (error) {
|
||||
renderData.error = 'Invalid Discord refresh token. Remove account and relink';
|
||||
return response.render('account/account', renderData);
|
||||
}
|
||||
|
||||
// TODO: Add a dedicated endpoint for updating connections?
|
||||
apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
}, {
|
||||
data: {
|
||||
id: account.connections.discord.id,
|
||||
access_token: tokens.access_token,
|
||||
refresh_token: tokens.refresh_token,
|
||||
}
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain : '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain : '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain : '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
}, {
|
||||
data: {
|
||||
id: account.connections.discord.id,
|
||||
access_token: tokens.access_token,
|
||||
refresh_token: tokens.refresh_token,
|
||||
}
|
||||
});
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
account.connections.discord.access_token = tokens.access_token;
|
||||
account.connections.discord.refresh_token = tokens.refresh_token;
|
||||
}
|
||||
|
||||
// Get the users Discord roles to check if they are a tester
|
||||
const { roles } = await discordOAuth.getMemberRolesForGuild({
|
||||
userId: account.connections.discord.id,
|
||||
guildId: config.discord.guild_id,
|
||||
botToken: config.discord.bot_token
|
||||
});
|
||||
|
||||
// Only run this check if not already a tester (edge case)
|
||||
if (!renderData.isTester) {
|
||||
// 409116477212459008 = Developer
|
||||
// 882247322933801030 = Super Mario (Patreon tier)
|
||||
renderData.isTester = roles.some(role => config.discord.tester_roles.includes(role));
|
||||
}
|
||||
} else {
|
||||
// If no Discord account linked, generate an auth URL
|
||||
const discordAuthURL = discordOAuth.generateAuthUrl({
|
||||
scope: ['identify', 'guilds'],
|
||||
state: crypto.randomBytes(16).toString('hex'),
|
||||
});
|
||||
|
||||
renderData.discordAuthURL = discordAuthURL;
|
||||
}
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
response.render('account/account', renderData);
|
||||
});
|
||||
|
||||
router.get('/login', async (request, response) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
error: request.cookies.error
|
||||
};
|
||||
|
||||
renderData.redirect = request.query.redirect;
|
||||
|
||||
response.clearCookie('error', { domain: '.pretendo.network' });
|
||||
|
||||
response.render('account/login', renderData);
|
||||
});
|
||||
|
||||
router.post('/login', async (request, response) => {
|
||||
const { username, password } = request.body;
|
||||
let { redirect } = request.body;
|
||||
|
||||
let apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
username,
|
||||
password,
|
||||
grant_type: 'password'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
response.cookie('error', apiResponse.body.error, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain : '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain : '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain : '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
|
||||
const account = apiResponse.body;
|
||||
|
||||
const hashedPassword = util.nintendoPasswordHash(password, account.pid);
|
||||
const hashedPasswordBuffer = Buffer.from(hashedPassword, 'hex');
|
||||
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', aesKey, Buffer.alloc(16));
|
||||
|
||||
let encryptedBody = cipher.update(hashedPasswordBuffer);
|
||||
encryptedBody = Buffer.concat([encryptedBody, cipher.final()]);
|
||||
|
||||
response.cookie('ph', encryptedBody.toString('hex'), { domain: '.pretendo.network' });
|
||||
|
||||
if (!redirect.startsWith('/')) {
|
||||
redirect = null;
|
||||
}
|
||||
|
||||
response.redirect(redirect || '/account');
|
||||
});
|
||||
|
||||
router.get('/register', async (request, response) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
error: request.cookies.error,
|
||||
email: request.cookies.email,
|
||||
username: request.cookies.username,
|
||||
mii_name: request.cookies.mii_name,
|
||||
};
|
||||
|
||||
response.clearCookie('error', { domain: '.pretendo.network' });
|
||||
response.clearCookie('email', { domain: '.pretendo.network' });
|
||||
response.clearCookie('username', { domain: '.pretendo.network' });
|
||||
response.clearCookie('mii_name', { domain: '.pretendo.network' });
|
||||
|
||||
let redirect = request.query.redirect;
|
||||
if (!redirect.startsWith('/')) {
|
||||
redirect = null;
|
||||
}
|
||||
|
||||
renderData.redirect = redirect;
|
||||
|
||||
response.render('account/register', renderData);
|
||||
});
|
||||
|
||||
router.post('/register', async (request, response) => {
|
||||
const { email, username, mii_name, password, password_confirm, 'h-captcha-response': hCaptchaResponse } = request.body;
|
||||
let { redirect } = request.body;
|
||||
|
||||
response.cookie('email', email, { domain: '.pretendo.network' });
|
||||
response.cookie('username', username, { domain: '.pretendo.network' });
|
||||
response.cookie('mii_name', mii_name, { domain: '.pretendo.network' });
|
||||
|
||||
const apiResponse = await util.apiPostGetRequest('/v1/register', {}, {
|
||||
email, username, mii_name, password, password_confirm, hCaptchaResponse
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
response.cookie('error', apiResponse.body.error, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/register');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
response.clearCookie('error', { domain: '.pretendo.network' });
|
||||
response.clearCookie('email', { domain: '.pretendo.network' });
|
||||
response.clearCookie('username', { domain: '.pretendo.network' });
|
||||
response.clearCookie('mii_name', { domain: '.pretendo.network' });
|
||||
|
||||
if (!redirect.startsWith('/')) {
|
||||
redirect = null;
|
||||
}
|
||||
|
||||
response.redirect(redirect || '/account');
|
||||
});
|
||||
|
||||
router.get('/logout', async(_request, response) => {
|
||||
response.clearCookie('refresh_token', { domain: '.pretendo.network' });
|
||||
response.clearCookie('access_token', { domain: '.pretendo.network' });
|
||||
response.clearCookie('token_type', { domain: '.pretendo.network' });
|
||||
response.clearCookie('ph', { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect('/');
|
||||
});
|
||||
|
||||
router.get('/connect/discord', async (request, response) => {
|
||||
let tokens;
|
||||
try {
|
||||
// Attempt to get OAuth2 tokens
|
||||
tokens = await discordOAuth.tokenRequest({
|
||||
code: request.query.code,
|
||||
scope: 'identify guilds',
|
||||
grantType: 'authorization_code',
|
||||
});
|
||||
} catch (error) {
|
||||
response.cookie('error', 'Invalid Discord authorization code. Please try again', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
|
||||
// Get Discord user data
|
||||
const user = await discordOAuth.getUser(tokens.access_token);
|
||||
|
||||
// Link the Discord account to the PNID
|
||||
let apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
}, {
|
||||
data: {
|
||||
id: user.id,
|
||||
access_token: tokens.access_token,
|
||||
refresh_token: tokens.refresh_token,
|
||||
}
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
}, {
|
||||
data: {
|
||||
id: user.id,
|
||||
access_token: tokens.access_token,
|
||||
refresh_token: tokens.refresh_token,
|
||||
}
|
||||
});
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success', 'Discord account linked successfully', { domain: '.pretendo.network' }).redirect('/account');
|
||||
});
|
||||
|
||||
router.get('/online-files', async (request, response) => {
|
||||
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token|| !request.cookies.ph) {
|
||||
return response.redirect('/account/login?redirect=/online-files');
|
||||
}
|
||||
|
||||
// Attempt to get user data
|
||||
let apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
}
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const account = apiResponse.body;
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, Buffer.alloc(16));
|
||||
|
||||
let decryptedPasswordHash = decipher.update(Buffer.from(request.cookies.ph, 'hex'));
|
||||
decryptedPasswordHash = Buffer.concat([decryptedPasswordHash, decipher.final()]);
|
||||
|
||||
const miiNameBuffer = Buffer.alloc(0x16);
|
||||
const miiName = Buffer.from(account.mii.name, 'utf16le').swap16();
|
||||
miiName.copy(miiNameBuffer);
|
||||
|
||||
let accountDat = 'AccountInstance_00000000\n';
|
||||
accountDat += 'PersistentId=80000001\n';
|
||||
accountDat += 'TransferableIdBase=0\n';
|
||||
accountDat += `Uuid=${uuidv4().replace(/-/g, '')}\n`;
|
||||
accountDat += `MiiData=${Buffer.from(account.mii.data, 'base64').toString('hex')}\n`;
|
||||
accountDat += `MiiName=${miiNameBuffer.toString('hex')}\n`;
|
||||
accountDat += `AccountId=${account.username}\n`;
|
||||
accountDat += 'BirthYear=0\n';
|
||||
accountDat += 'BirthMonth=0\n';
|
||||
accountDat += 'BirthDay=0\n';
|
||||
accountDat += 'Gender=0\n';
|
||||
accountDat += `EmailAddress=${account.email.address}\n`;
|
||||
accountDat += 'Country=0\n';
|
||||
accountDat += 'SimpleAddressId=0\n';
|
||||
accountDat += `PrincipalId=${account.pid.toString(16)}\n`;
|
||||
accountDat += 'IsPasswordCacheEnabled=1\n';
|
||||
accountDat += `AccountPasswordCache=${decryptedPasswordHash.toString('hex')}`;
|
||||
|
||||
const onlineFiles = new AdmZip();
|
||||
|
||||
onlineFiles.addFile('mlc01/usr/save/system/act/80000001/account.dat', Buffer.from(accountDat)); // Minimal account.dat
|
||||
onlineFiles.addFile('otp.bin', Buffer.alloc(0x400)); // nulled OTP
|
||||
onlineFiles.addFile('seeprom.bin', Buffer.alloc(0x200)); // nulled SEEPROM
|
||||
|
||||
response.status(200);
|
||||
response.set('Content-Disposition', 'attachment; filename="Online Files.zip');
|
||||
response.set('Content-Type', 'application/zip');
|
||||
|
||||
response.end(onlineFiles.toBuffer());
|
||||
});
|
||||
|
||||
router.get('/miieditor', async (request, response) => {
|
||||
|
||||
const reqLocale = request.locale;
|
||||
const locale = util.getLocale(reqLocale.region, reqLocale.language);
|
||||
|
||||
// Should obviously be the user's
|
||||
const encodedUserMiiData = 'AwAAQOlVognnx0GC2X0LLQOzuI0n2QAAAUBiAGUAbABsAGEAAABFAAAAAAAAAEBAEgCBAQRoQxggNEYUgRIXaA0AACkDUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP6G';
|
||||
|
||||
// Adapted from https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values
|
||||
|
||||
const editorToHex = {
|
||||
'face': [
|
||||
0x00,0x01,0x08,0x02,0x03,0x09,0x04,0x05,0x0a,0x06,0x07,0x0b
|
||||
],
|
||||
'hairs': [
|
||||
[0x21,0x2f,0x28,0x25,0x20,0x6b,0x30,0x33,0x37,0x46,0x2c,0x42],
|
||||
[0x34,0x32,0x26,0x31,0x2b,0x1f,0x38,0x44,0x3e,0x73,0x4c,0x77],
|
||||
[0x40,0x51,0x74,0x79,0x16,0x3a,0x3c,0x57,0x7d,0x75,0x49,0x4b],
|
||||
[0x2a,0x59,0x39,0x36,0x50,0x22,0x17,0x56,0x58,0x76,0x27,0x24],
|
||||
[0x2d,0x43,0x3b,0x41,0x29,0x1e,0x0c,0x10,0x0a,0x52,0x80,0x81],
|
||||
[0x0e,0x5f,0x69,0x64,0x06,0x14,0x5d,0x66,0x1b,0x04,0x11,0x6e],
|
||||
[0x7b,0x08,0x6a,0x48,0x03,0x15,0x00,0x62,0x3f,0x5a,0x0b,0x78],
|
||||
[0x05,0x4a,0x6c,0x5e,0x7c,0x19,0x63,0x45,0x23,0x0d,0x7a,0x71],
|
||||
[0x35,0x18,0x55,0x53,0x47,0x83,0x60,0x65,0x1d,0x07,0x0f,0x70],
|
||||
[0x4f,0x01,0x6d,0x7f,0x5b,0x1a,0x3d,0x67,0x02,0x4d,0x12,0x5c],
|
||||
[0x54,0x09,0x13,0x82,0x61,0x68,0x2e,0x4e,0x1c,0x72,0x7e,0x6f]
|
||||
],
|
||||
'eyebrows': [
|
||||
[0x06,0x00,0x0c,0x01,0x09,0x13,0x07,0x15,0x08,0x11,0x05,0x04],
|
||||
[0x0b,0x0a,0x02,0x03,0x0e,0x14,0x0f,0x0d,0x16,0x12,0x10,0x17]
|
||||
],
|
||||
'eyes': [
|
||||
[0x02,0x04,0x00,0x08,0x27,0x11,0x01,0x1a,0x10,0x0f,0x1b,0x14],
|
||||
[0x21,0x0b,0x13,0x20,0x09,0x0c,0x17,0x22,0x15,0x19,0x28,0x23],
|
||||
[0x05,0x29,0x0d,0x24,0x25,0x06,0x18,0x1e,0x1f,0x12,0x1c,0x2e],
|
||||
[0x07,0x2c,0x26,0x2a,0x2d,0x1d,0x03,0x2b,0x16,0x0a,0x0e,0x2f],
|
||||
[0x30,0x31,0x32,0x35,0x3b,0x38,0x36,0x3a,0x39,0x37,0x33,0x34]
|
||||
],
|
||||
'nose': [
|
||||
[0x01,0x0a,0x02,0x03,0x06,0x00, 0x05,0x04,0x08,0x09,0x07,0x0B],
|
||||
[0x0d,0x0e,0x0c,0x11,0x10,0x0f]
|
||||
],
|
||||
'mouth': [
|
||||
[0x17,0x01,0x13,0x15,0x16,0x05,0x00,0x08,0x0a,0x10,0x06,0x0d],
|
||||
[0x07,0x09,0x02,0x11,0x03,0x04,0x0f,0x0b,0x14,0x12,0x0e,0x0c],
|
||||
[0x1b,0x1e,0x18,0x19,0x1d,0x1c,0x1a,0x23,0x1f,0x22,0x21,0x20]
|
||||
]
|
||||
};
|
||||
|
||||
response.render('account/miieditor', {
|
||||
layout: 'main',
|
||||
locale,
|
||||
localeString: reqLocale.toString(),
|
||||
encodedUserMiiData,
|
||||
editorToHex
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/upgrade', async (request, response) => {
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) {
|
||||
return response.redirect('/account/login?redirect=/account/upgrade');
|
||||
}
|
||||
|
||||
// Attempt to get user data
|
||||
let apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
}
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
// Set user account info to render data
|
||||
const account = apiResponse.body;
|
||||
const pid = account.pid;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
error: request.cookies.error,
|
||||
currentTier: pnid.get('connections.stripe.price_id'),
|
||||
donationCache: await cache.getStripeDonationCache()
|
||||
};
|
||||
|
||||
const { data: prices } = await stripe.prices.list();
|
||||
const { data: products } = await stripe.products.list();
|
||||
|
||||
renderData.tiers = products
|
||||
.filter(product => product.active)
|
||||
.sort((a, b) => +a.metadata.tier_level - +b.metadata.tier_level)
|
||||
.map(product => {
|
||||
const price = prices.find(price => price.product === product.id);
|
||||
const perks = [];
|
||||
|
||||
if (product.metadata.discord_read === 'true') {
|
||||
perks.push('Read-only access to select dev channels on Discord');
|
||||
}
|
||||
|
||||
if (product.metadata.beta === 'true') {
|
||||
perks.push('Access the beta servers');
|
||||
}
|
||||
|
||||
return {
|
||||
price_id: price.id,
|
||||
thumbnail: product.images[0],
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
perks,
|
||||
price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }),
|
||||
};
|
||||
});
|
||||
|
||||
response.render('account/upgrade', renderData);
|
||||
});
|
||||
|
||||
router.post('/stripe/checkout/:priceId', async (request, response) => {
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
// Attempt to get user data
|
||||
let apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
}
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
// Set user account info to render data
|
||||
const account = apiResponse.body;
|
||||
const pid = account.pid;
|
||||
|
||||
let customer;
|
||||
const { data: searchResults } = await stripe.customers.search({
|
||||
query: `metadata['pnid_pid']:'${pid}'`
|
||||
});
|
||||
|
||||
if (searchResults.length !== 0) {
|
||||
customer = searchResults[0];
|
||||
} else {
|
||||
customer = await stripe.customers.create({
|
||||
email: account.email.address,
|
||||
metadata: {
|
||||
pnid_pid: pid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, {
|
||||
$set: {
|
||||
'connections.stripe.customer_id': customer.id // ensure PNID always has latest customer ID
|
||||
}
|
||||
}, { upsert: true }).exec();
|
||||
|
||||
const priceId = request.params.priceId;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
if (pnid.get('access_level') >= 2) {
|
||||
response.cookie('error', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
customer: customer.id,
|
||||
mode: 'subscription',
|
||||
success_url: `${config.http.base_url}/account?upgrade_success=true`,
|
||||
cancel_url: `${config.http.base_url}/account?upgrade_success=false`
|
||||
});
|
||||
|
||||
return response.redirect(303, session.url);
|
||||
} catch (error) {
|
||||
// Maybe we need a dedicated error page?
|
||||
// Or handle this as not cookies?
|
||||
response.cookie('error', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/stripe/unsubscribe', async (request, response) => {
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
// Attempt to get user data
|
||||
let apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
}
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
// Set user account info to render data
|
||||
const account = apiResponse.body;
|
||||
const pid = account.pid;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
const subscriptionId = pnid.get('connections.stripe.subscription_id');
|
||||
const tierName = pnid.get('connections.stripe.tier_name');
|
||||
|
||||
if (subscriptionId) {
|
||||
try {
|
||||
await stripe.subscriptions.del(subscriptionId);
|
||||
|
||||
const updateData = {
|
||||
'connections.stripe.subscription_id': null,
|
||||
'connections.stripe.price_id': null,
|
||||
'connections.stripe.tier_level': 0,
|
||||
'connections.stripe.tier_name': null,
|
||||
};
|
||||
|
||||
if (pnid.get('access_level') < 2) {
|
||||
// Fail-safe for if staff members reach here
|
||||
// Mostly only useful during testing
|
||||
updateData.access_level = 0;
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, { $set: updateData }).exec();
|
||||
} catch (error) {
|
||||
logger.error(`Error canceling old user subscription | ${pnid.get('connections.stripe.customer_id')}, ${pid}, ${subscriptionId} | - ${error.message}`);
|
||||
|
||||
response.cookie('error', 'Error canceling subscription! Contact support if issue persists', { domain: '.pretendo.network' });
|
||||
|
||||
return response.redirect('/account');
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success', `Unsubscribed from ${tierName}`, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
});
|
||||
|
||||
router.post('/stripe/webhook', express.raw({ type: 'application/json' }), async (request, response) => {
|
||||
const stripeSignature = request.headers['stripe-signature'];
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(request.body, stripeSignature, config.stripe.webhook_secret);
|
||||
} catch (err) {
|
||||
return response.status(400).send(`Webhook Error: ${err.message}`);
|
||||
}
|
||||
|
||||
await util.handleStripeEvent(event);
|
||||
|
||||
response.json({ received: true });
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,23 +0,0 @@
|
||||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
response.render('aprilfools', renderData);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,22 +0,0 @@
|
||||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
response.render('localization', renderData);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
424
src/routes/account.js
Normal file
424
src/routes/account.js
Normal file
@@ -0,0 +1,424 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const DiscordOauth2 = require('discord-oauth2');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const AdmZip = require('adm-zip');
|
||||
const Stripe = require('stripe');
|
||||
const { REST: DiscordRest } = require('@discordjs/rest');
|
||||
const { Routes: DiscordRoutes } = require('discord-api-types/v10');
|
||||
const requireLoginMiddleware = require('../middleware/require-login');
|
||||
const database = require('../database');
|
||||
const cache = require('../cache');
|
||||
const util = require('../util');
|
||||
const logger = require('../logger');
|
||||
const config = require('../../config.json');
|
||||
|
||||
const { Router } = express;
|
||||
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
const router = new Router();
|
||||
const discordRest = new DiscordRest({ version: '10' }).setToken(config.discord.bot_token);
|
||||
|
||||
// Create OAuth client
|
||||
const discordOAuth = new DiscordOauth2({
|
||||
clientId: config.discord.client_id,
|
||||
clientSecret: config.discord.client_secret,
|
||||
redirectUri: `${config.http.base_url}/account/connect/discord`,
|
||||
version: 'v10'
|
||||
});
|
||||
|
||||
router.get('/', requireLoginMiddleware, async (request, response) => {
|
||||
// Setup the data to be sent to the handlebars renderer
|
||||
const renderData = {};
|
||||
|
||||
// Check for Stripe messages
|
||||
const { upgrade_success } = request.query;
|
||||
|
||||
if (upgrade_success === 'true') {
|
||||
renderData.success_message = 'Account upgraded successfully';
|
||||
} else if (upgrade_success === 'false') {
|
||||
renderData.error_message = 'Account upgrade failed';
|
||||
}
|
||||
|
||||
const { account } = request;
|
||||
const { pnid } = request;
|
||||
|
||||
renderData.tierName = pnid.get('connections.stripe.tier_name');
|
||||
renderData.tierLevel = pnid.get('connections.stripe.tier_level');
|
||||
renderData.account = account;
|
||||
renderData.isTester = account.access_level > 0;
|
||||
|
||||
// Check if a Discord account is linked to the PNID
|
||||
if (account.connections.discord.id && account.connections.discord.id.trim() !== '') {
|
||||
try {
|
||||
renderData.discordUser = await discordRest.get(DiscordRoutes.user(account.connections.discord.id));
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
}
|
||||
} else {
|
||||
// If no Discord account linked, generate an auth URL
|
||||
const discordAuthURL = discordOAuth.generateAuthUrl({
|
||||
scope: ['identify', 'guilds'],
|
||||
state: crypto.randomBytes(16).toString('hex'),
|
||||
});
|
||||
|
||||
renderData.discordAuthURL = discordAuthURL;
|
||||
}
|
||||
|
||||
response.render('account/account', renderData);
|
||||
});
|
||||
|
||||
router.get('/login', async (request, response) => {
|
||||
response.render('account/login');
|
||||
});
|
||||
|
||||
router.post('/login', async (request, response) => {
|
||||
const { username, password } = request.body;
|
||||
|
||||
try {
|
||||
const tokens = await util.login(username, password);
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect(request.redirect || '/account');
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/register', async (request, response) => {
|
||||
const renderData = {
|
||||
email: request.cookies.email,
|
||||
username: request.cookies.username,
|
||||
mii_name: request.cookies.mii_name,
|
||||
};
|
||||
|
||||
response.clearCookie('email', { domain: '.pretendo.network' });
|
||||
response.clearCookie('username', { domain: '.pretendo.network' });
|
||||
response.clearCookie('mii_name', { domain: '.pretendo.network' });
|
||||
|
||||
response.render('account/register', renderData);
|
||||
});
|
||||
|
||||
router.post('/register', async (request, response) => {
|
||||
const { email, username, mii_name, password, password_confirm, 'h-captcha-response': hCaptchaResponse } = request.body;
|
||||
|
||||
response.cookie('email', email, { domain: '.pretendo.network' });
|
||||
response.cookie('username', username, { domain: '.pretendo.network' });
|
||||
response.cookie('mii_name', mii_name, { domain: '.pretendo.network' });
|
||||
|
||||
try {
|
||||
const tokens = await util.register({
|
||||
email,
|
||||
username,
|
||||
mii_name,
|
||||
password,
|
||||
password_confirm,
|
||||
hCaptchaResponse
|
||||
});
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
response.clearCookie('email', { domain: '.pretendo.network' });
|
||||
response.clearCookie('username', { domain: '.pretendo.network' });
|
||||
response.clearCookie('mii_name', { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect(request.redirect || '/account');
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/register');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/logout', async(_request, response) => {
|
||||
response.clearCookie('refresh_token', { domain: '.pretendo.network' });
|
||||
response.clearCookie('access_token', { domain: '.pretendo.network' });
|
||||
response.clearCookie('token_type', { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect('/');
|
||||
});
|
||||
|
||||
router.get('/connect/discord', requireLoginMiddleware, async (request, response) => {
|
||||
let tokens;
|
||||
try {
|
||||
// Attempt to get OAuth2 tokens
|
||||
tokens = await discordOAuth.tokenRequest({
|
||||
code: request.query.code,
|
||||
scope: 'identify guilds',
|
||||
grantType: 'authorization_code',
|
||||
});
|
||||
} catch (error) {
|
||||
response.cookie('error_message', 'Invalid Discord authorization code. Please try again', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
|
||||
// Get Discord user data
|
||||
const discordUser = await discordOAuth.getUser(tokens.access_token);
|
||||
|
||||
try {
|
||||
await util.updateDiscordConnection(discordUser, request, response);
|
||||
|
||||
response.cookie('success_message', 'Discord account linked successfully', { domain: '.pretendo.network' });
|
||||
response.redirect('/account');
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/online-files', requireLoginMiddleware, async (request, response) => {
|
||||
const { account } = request;
|
||||
const { password } = request.body;
|
||||
|
||||
const hashedPassword = util.nintendoPasswordHash(password, account.pid);
|
||||
|
||||
const miiNameBuffer = Buffer.alloc(0x16);
|
||||
const miiName = Buffer.from(account.mii.name, 'utf16le').swap16();
|
||||
miiName.copy(miiNameBuffer);
|
||||
|
||||
let accountDat = 'AccountInstance_00000000\n';
|
||||
accountDat += 'PersistentId=80000001\n';
|
||||
accountDat += 'TransferableIdBase=0\n';
|
||||
accountDat += `Uuid=${uuidv4().replace(/-/g, '')}\n`;
|
||||
accountDat += `MiiData=${Buffer.from(account.mii.data, 'base64').toString('hex')}\n`;
|
||||
accountDat += `MiiName=${miiNameBuffer.toString('hex')}\n`;
|
||||
accountDat += `AccountId=${account.username}\n`;
|
||||
accountDat += 'BirthYear=0\n';
|
||||
accountDat += 'BirthMonth=0\n';
|
||||
accountDat += 'BirthDay=0\n';
|
||||
accountDat += 'Gender=0\n';
|
||||
accountDat += `EmailAddress=${account.email.address}\n`;
|
||||
accountDat += 'Country=0\n';
|
||||
accountDat += 'SimpleAddressId=0\n';
|
||||
accountDat += `PrincipalId=${account.pid.toString(16)}\n`;
|
||||
accountDat += 'IsPasswordCacheEnabled=1\n';
|
||||
accountDat += `AccountPasswordCache=${hashedPassword}`;
|
||||
|
||||
const onlineFiles = new AdmZip();
|
||||
|
||||
onlineFiles.addFile('mlc01/usr/save/system/act/80000001/account.dat', Buffer.from(accountDat)); // Minimal account.dat
|
||||
onlineFiles.addFile('otp.bin', Buffer.alloc(0x400)); // nulled OTP
|
||||
onlineFiles.addFile('seeprom.bin', Buffer.alloc(0x200)); // nulled SEEPROM
|
||||
|
||||
response.status(200);
|
||||
response.set('Content-Disposition', 'attachment; filename="Cemu Pretendo Online Files.zip');
|
||||
response.set('Content-Type', 'application/zip');
|
||||
|
||||
response.end(onlineFiles.toBuffer());
|
||||
});
|
||||
|
||||
router.get('/miieditor', requireLoginMiddleware, async (request, response) => {
|
||||
const { account } = request;
|
||||
|
||||
// Adapted from https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values
|
||||
|
||||
const editorToHex = {
|
||||
'face': [
|
||||
0x00,0x01,0x08,0x02,0x03,0x09,0x04,0x05,0x0a,0x06,0x07,0x0b
|
||||
],
|
||||
'hairs': [
|
||||
[0x21,0x2f,0x28,0x25,0x20,0x6b,0x30,0x33,0x37,0x46,0x2c,0x42],
|
||||
[0x34,0x32,0x26,0x31,0x2b,0x1f,0x38,0x44,0x3e,0x73,0x4c,0x77],
|
||||
[0x40,0x51,0x74,0x79,0x16,0x3a,0x3c,0x57,0x7d,0x75,0x49,0x4b],
|
||||
[0x2a,0x59,0x39,0x36,0x50,0x22,0x17,0x56,0x58,0x76,0x27,0x24],
|
||||
[0x2d,0x43,0x3b,0x41,0x29,0x1e,0x0c,0x10,0x0a,0x52,0x80,0x81],
|
||||
[0x0e,0x5f,0x69,0x64,0x06,0x14,0x5d,0x66,0x1b,0x04,0x11,0x6e],
|
||||
[0x7b,0x08,0x6a,0x48,0x03,0x15,0x00,0x62,0x3f,0x5a,0x0b,0x78],
|
||||
[0x05,0x4a,0x6c,0x5e,0x7c,0x19,0x63,0x45,0x23,0x0d,0x7a,0x71],
|
||||
[0x35,0x18,0x55,0x53,0x47,0x83,0x60,0x65,0x1d,0x07,0x0f,0x70],
|
||||
[0x4f,0x01,0x6d,0x7f,0x5b,0x1a,0x3d,0x67,0x02,0x4d,0x12,0x5c],
|
||||
[0x54,0x09,0x13,0x82,0x61,0x68,0x2e,0x4e,0x1c,0x72,0x7e,0x6f]
|
||||
],
|
||||
'eyebrows': [
|
||||
[0x06,0x00,0x0c,0x01,0x09,0x13,0x07,0x15,0x08,0x11,0x05,0x04],
|
||||
[0x0b,0x0a,0x02,0x03,0x0e,0x14,0x0f,0x0d,0x16,0x12,0x10,0x17]
|
||||
],
|
||||
'eyes': [
|
||||
[0x02,0x04,0x00,0x08,0x27,0x11,0x01,0x1a,0x10,0x0f,0x1b,0x14],
|
||||
[0x21,0x0b,0x13,0x20,0x09,0x0c,0x17,0x22,0x15,0x19,0x28,0x23],
|
||||
[0x05,0x29,0x0d,0x24,0x25,0x06,0x18,0x1e,0x1f,0x12,0x1c,0x2e],
|
||||
[0x07,0x2c,0x26,0x2a,0x2d,0x1d,0x03,0x2b,0x16,0x0a,0x0e,0x2f],
|
||||
[0x30,0x31,0x32,0x35,0x3b,0x38,0x36,0x3a,0x39,0x37,0x33,0x34]
|
||||
],
|
||||
'nose': [
|
||||
[0x01,0x0a,0x02,0x03,0x06,0x00, 0x05,0x04,0x08,0x09,0x07,0x0B],
|
||||
[0x0d,0x0e,0x0c,0x11,0x10,0x0f]
|
||||
],
|
||||
'mouth': [
|
||||
[0x17,0x01,0x13,0x15,0x16,0x05,0x00,0x08,0x0a,0x10,0x06,0x0d],
|
||||
[0x07,0x09,0x02,0x11,0x03,0x04,0x0f,0x0b,0x14,0x12,0x0e,0x0c],
|
||||
[0x1b,0x1e,0x18,0x19,0x1d,0x1c,0x1a,0x23,0x1f,0x22,0x21,0x20]
|
||||
]
|
||||
};
|
||||
|
||||
response.render('account/miieditor', {
|
||||
encodedUserMiiData: account.mii.data,
|
||||
editorToHex
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/upgrade', requireLoginMiddleware, async (request, response) => {
|
||||
// Set user account info to render data
|
||||
const { pnid } = request;
|
||||
|
||||
const renderData = {
|
||||
error: request.cookies.error,
|
||||
currentTier: pnid.get('connections.stripe.price_id'),
|
||||
donationCache: await cache.getStripeDonationCache()
|
||||
};
|
||||
|
||||
const { data: prices } = await stripe.prices.list();
|
||||
const { data: products } = await stripe.products.list();
|
||||
|
||||
renderData.tiers = products
|
||||
.filter(product => product.active)
|
||||
.sort((a, b) => +a.metadata.tier_level - +b.metadata.tier_level)
|
||||
.map(product => {
|
||||
const price = prices.find(price => price.product === product.id);
|
||||
const perks = [];
|
||||
|
||||
if (product.metadata.discord_read === 'true') {
|
||||
perks.push('Read-only access to select dev channels on Discord');
|
||||
}
|
||||
|
||||
if (product.metadata.beta === 'true') {
|
||||
perks.push('Access the beta servers');
|
||||
}
|
||||
|
||||
return {
|
||||
price_id: price.id,
|
||||
thumbnail: product.images[0],
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
perks,
|
||||
price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }),
|
||||
};
|
||||
});
|
||||
|
||||
response.render('account/upgrade', renderData);
|
||||
});
|
||||
|
||||
router.post('/stripe/checkout/:priceId', requireLoginMiddleware, async (request, response) => {
|
||||
// Set user account info to render data
|
||||
const { account } = request;
|
||||
const pid = account.pid;
|
||||
|
||||
let customer;
|
||||
const { data: searchResults } = await stripe.customers.search({
|
||||
query: `metadata['pnid_pid']:'${pid}'`
|
||||
});
|
||||
|
||||
if (searchResults.length !== 0) {
|
||||
customer = searchResults[0];
|
||||
} else {
|
||||
customer = await stripe.customers.create({
|
||||
email: account.email.address,
|
||||
metadata: {
|
||||
pnid_pid: pid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, {
|
||||
$set: {
|
||||
'connections.stripe.customer_id': customer.id, // ensure PNID always has latest customer ID
|
||||
'connections.stripe.latest_webhook_timestamp': 0
|
||||
}
|
||||
}, { upsert: true }).exec();
|
||||
|
||||
const priceId = request.params.priceId;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
if (pnid.get('access_level') >= 2) {
|
||||
response.cookie('error_message', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
customer: customer.id,
|
||||
mode: 'subscription',
|
||||
success_url: `${config.http.base_url}/account?upgrade_success=true`,
|
||||
cancel_url: `${config.http.base_url}/account?upgrade_success=false`
|
||||
});
|
||||
|
||||
return response.redirect(303, session.url);
|
||||
} catch (error) {
|
||||
// Maybe we need a dedicated error page?
|
||||
// Or handle this as not cookies?
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
|
||||
return response.redirect('/account');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/stripe/unsubscribe', requireLoginMiddleware, async (request, response) => {
|
||||
// Set user account info to render data
|
||||
const { pnid } = request;
|
||||
|
||||
const pid = pnid.get('pid');
|
||||
const subscriptionId = pnid.get('connections.stripe.subscription_id');
|
||||
const tierName = pnid.get('connections.stripe.tier_name');
|
||||
|
||||
if (subscriptionId) {
|
||||
try {
|
||||
await stripe.subscriptions.del(subscriptionId);
|
||||
|
||||
const updateData = {
|
||||
'connections.stripe.subscription_id': null,
|
||||
'connections.stripe.price_id': null,
|
||||
'connections.stripe.tier_level': 0,
|
||||
'connections.stripe.tier_name': null,
|
||||
};
|
||||
|
||||
if (pnid.get('access_level') < 2) {
|
||||
// Fail-safe for if staff members reach here
|
||||
// Mostly only useful during testing
|
||||
updateData.access_level = 0;
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, { $set: updateData }).exec();
|
||||
} catch (error) {
|
||||
logger.error(`Error canceling old user subscription | ${pnid.get('connections.stripe.customer_id')}, ${pid}, ${subscriptionId} | - ${error.message}`);
|
||||
|
||||
response.cookie('error_message', 'Error canceling subscription! Contact support if issue persists', { domain: '.pretendo.network' });
|
||||
|
||||
return response.redirect('/account');
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success', `Unsubscribed from ${tierName}`, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
});
|
||||
|
||||
router.post('/stripe/webhook', express.raw({ type: 'application/json' }), async (request, response) => {
|
||||
const stripeSignature = request.headers['stripe-signature'];
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(request.body, stripeSignature, config.stripe.webhook_secret);
|
||||
} catch (err) {
|
||||
return response.status(400).send(`Webhook Error: ${err.message}`);
|
||||
}
|
||||
|
||||
await util.handleStripeEvent(event);
|
||||
|
||||
response.json({ received: true });
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
8
src/routes/aprilfools.js
Normal file
8
src/routes/aprilfools.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
response.render('aprilfools');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,4 @@
|
||||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const logger = require('../logger');
|
||||
const router = new Router();
|
||||
|
||||
@@ -37,19 +36,9 @@ const postList = () => {
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
postList,
|
||||
postList
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
response.render('blog/blog', renderData);
|
||||
});
|
||||
|
||||
@@ -74,18 +63,9 @@ router.get('/:slug', async (request, response, next) => {
|
||||
|
||||
const renderData = {
|
||||
layout: 'blog-opengraph',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
postList,
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
// Get the name of the post from the URL
|
||||
const postName = request.params.slug;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const router = new Router();
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -12,44 +11,24 @@ router.get('/', async (request, response) => {
|
||||
|
||||
router.get('/search', async (request, response) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
currentPage: request.params.slug,
|
||||
currentPage: request.params.slug
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
response.render('docs/search', renderData);
|
||||
});
|
||||
|
||||
router.get('/:slug', async (request, response, next) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
currentPage: request.params.slug,
|
||||
currentPage: request.params.slug
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
// Get the name of the page from the URL
|
||||
const pageName = request.params.slug;
|
||||
|
||||
let markdownLocale = renderData.localeString;
|
||||
let markdownLocale = response.locals.localeString;
|
||||
let missingInLocale = false;
|
||||
// Check if the MD file exists in the user's locale, if not try en-US and show notice, or finally log error and show 404.
|
||||
if (fs.existsSync(path.join('docs', renderData.localeString, `${pageName}.md`))) {
|
||||
if (fs.existsSync(path.join('docs', markdownLocale, `${pageName}.md`))) {
|
||||
null;
|
||||
} else if (fs.existsSync(path.join('docs', 'en-US', `${pageName}.md`))) {
|
||||
markdownLocale = 'en-US';
|
||||
@@ -1,5 +1,4 @@
|
||||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const { boards } = require('../../boards/boards.json');
|
||||
const router = new Router();
|
||||
|
||||
@@ -8,25 +7,15 @@ const { getTrelloCache } = require('../cache');
|
||||
router.get('/', async (request, response) => {
|
||||
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
boards,
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
boards
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
const cache = await getTrelloCache();
|
||||
|
||||
// Builds the arrays of people for the special thanks section
|
||||
|
||||
// Shuffles the special thanks people
|
||||
const specialThanksPeople = renderData.locale.specialThanks.people.slice();
|
||||
const specialThanksPeople = response.locals.locale.specialThanks.people.slice();
|
||||
function shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
8
src/routes/localization.js
Normal file
8
src/routes/localization.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
response.render('localization');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,4 @@
|
||||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const { boards } = require('../../boards/boards.json');
|
||||
const router = new Router();
|
||||
|
||||
@@ -7,19 +6,9 @@ const { getTrelloCache, getStripeDonationCache } = require('../cache');
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
boards,
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
boards
|
||||
};
|
||||
|
||||
renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph;
|
||||
|
||||
if (renderData.isLoggedIn) {
|
||||
const account = await util.getAccount(request, response);
|
||||
renderData.account = account;
|
||||
}
|
||||
|
||||
const trelloCache = await getTrelloCache();
|
||||
renderData.progressLists = trelloCache;
|
||||
const stripeDonationCache = await getStripeDonationCache();
|
||||
@@ -9,6 +9,9 @@ const PNIDSchema = new Schema({
|
||||
server_access_level: String,
|
||||
access_level: Number,
|
||||
connections: {
|
||||
discord: {
|
||||
id: String
|
||||
},
|
||||
stripe: {
|
||||
customer_id: String,
|
||||
subscription_id: String,
|
||||
|
||||
@@ -6,9 +6,11 @@ const morgan = require('morgan');
|
||||
const expressLocale = require('express-locale');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const Stripe = require('stripe');
|
||||
const logger = require('./logger');
|
||||
const redirectMiddleware = require('./middleware/redirect');
|
||||
const renderDataMiddleware = require('./middleware/render-data');
|
||||
const database = require('./database');
|
||||
const util = require('./util');
|
||||
const logger = require('./logger');
|
||||
const config = require('../config.json');
|
||||
const defaultLocale = require('../locales/US_en.json');
|
||||
|
||||
@@ -18,26 +20,11 @@ const stripe = new Stripe(config.stripe.secret_key);
|
||||
|
||||
logger.info('Setting up Middleware');
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
logger.info('Setting up static public folder');
|
||||
app.use(express.static('public'));
|
||||
|
||||
logger.info('Importing page routers');
|
||||
const routers = {
|
||||
home: require('./routers/home'),
|
||||
faq: require('./routers/faq'),
|
||||
docs: require('./routers/docs'),
|
||||
progress: require('./routers/progress'),
|
||||
account: require('./routers/account'),
|
||||
blog: require('./routers/blog'),
|
||||
localization: require('./routers/localization'),
|
||||
aprilfools: require('./routers/aprilfools')
|
||||
};
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
app.use(cookieParser());
|
||||
|
||||
// Locale express middleware setup
|
||||
app.use(expressLocale({
|
||||
'priority': ['cookie', 'accept-language', 'map', 'default'],
|
||||
cookie: { name: 'preferredLocale' },
|
||||
@@ -82,15 +69,32 @@ app.use(expressLocale({
|
||||
],
|
||||
'default': 'en-US'
|
||||
}));
|
||||
app.use(redirectMiddleware);
|
||||
app.use(renderDataMiddleware);
|
||||
|
||||
app.use('/', routers.home);
|
||||
app.use('/faq', routers.faq);
|
||||
app.use('/docs', routers.docs);
|
||||
app.use('/progress', routers.progress);
|
||||
app.use('/account', routers.account);
|
||||
app.use('/localization', routers.localization);
|
||||
app.use('/blog', routers.blog);
|
||||
app.use('/nso-legacy-pack', routers.aprilfools);
|
||||
logger.info('Setting up static public folder');
|
||||
app.use(express.static('public'));
|
||||
|
||||
logger.info('Importing routes');
|
||||
const routes = {
|
||||
home: require('./routes/home'),
|
||||
faq: require('./routes/faq'),
|
||||
docs: require('./routes/docs'),
|
||||
progress: require('./routes/progress'),
|
||||
account: require('./routes/account'),
|
||||
blog: require('./routes/blog'),
|
||||
localization: require('./routes/localization'),
|
||||
aprilfools: require('./routes/aprilfools')
|
||||
};
|
||||
|
||||
app.use('/', routes.home);
|
||||
app.use('/faq', routes.faq);
|
||||
app.use('/docs', routes.docs);
|
||||
app.use('/progress', routes.progress);
|
||||
app.use('/account', routes.account);
|
||||
app.use('/localization', routes.localization);
|
||||
app.use('/blog', routes.blog);
|
||||
app.use('/nso-legacy-pack', routes.aprilfools);
|
||||
|
||||
logger.info('Creating 404 status handler');
|
||||
// This works because it is the last router created
|
||||
|
||||
128
src/util.js
128
src/util.js
@@ -59,6 +59,86 @@ function apiDeleteGetRequest(path, headers, json) {
|
||||
});
|
||||
}
|
||||
|
||||
async function register(registerData) {
|
||||
const apiResponse = await apiPostGetRequest('/v1/register', {}, registerData);
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
throw new Error(apiResponse.body.error);
|
||||
}
|
||||
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async function login(username, password) {
|
||||
const apiResponse = await apiPostGetRequest('/v1/login', {}, {
|
||||
username,
|
||||
password,
|
||||
grant_type: 'password'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
throw new Error(apiResponse.body.error);
|
||||
}
|
||||
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async function refreshLogin(request, response) {
|
||||
const apiResponse = await apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
}
|
||||
|
||||
async function getUserAccountData(request, response, fromRetry=false) {
|
||||
const apiResponse = await apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200 && fromRetry === true) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
await refreshLogin(request, response);
|
||||
return await getUserAccountData(request, response, true);
|
||||
}
|
||||
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async function updateDiscordConnection(discordUser, request, response, fromRetry=false) {
|
||||
const apiResponse = await apiPostGetRequest('/v1/connections/add/discord', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
}, {
|
||||
data: {
|
||||
id: discordUser.id
|
||||
}
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200 && fromRetry === true) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
await refreshLogin(request, response);
|
||||
await updateDiscordConnection(discordUser, request, response, true);
|
||||
}
|
||||
}
|
||||
|
||||
function nintendoPasswordHash(password, pid) {
|
||||
const pidBuffer = Buffer.alloc(4);
|
||||
pidBuffer.writeUInt32LE(pid);
|
||||
@@ -255,53 +335,17 @@ async function handleStripeEvent(event) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getAccount(request, response) {
|
||||
// Attempt to get user data
|
||||
let apiResponse = await apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// Assume expired, refresh and retry request
|
||||
apiResponse = await apiPostGetRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
apiResponse = await apiGetRequest('/v1/user', {
|
||||
'Authorization': `${tokens.token_type} ${tokens.access_token}`
|
||||
});
|
||||
}
|
||||
|
||||
// If still failed, something went horribly wrong
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
// Return user account info
|
||||
const account = apiResponse.body;
|
||||
return account;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fullUrl,
|
||||
getLocale,
|
||||
apiGetRequest,
|
||||
apiPostGetRequest,
|
||||
apiDeleteGetRequest,
|
||||
register,
|
||||
login,
|
||||
refreshLogin,
|
||||
getUserAccountData,
|
||||
updateDiscordConnection,
|
||||
nintendoPasswordHash,
|
||||
handleStripeEvent,
|
||||
getAccount
|
||||
handleStripeEvent
|
||||
};
|
||||
@@ -139,7 +139,7 @@
|
||||
<p>Connected to Discord as {{ discordUser.username }}#{{ discordUser.discriminator }}</p>
|
||||
<button class="button secondary" id="remove-discord-connection">Remove Discord account</button>
|
||||
{{else}}
|
||||
<p>To link your Discord account, run /whatever in the <a href="https://invite.gg/pretendo" >Discord server</a></p>
|
||||
<p>No Discord account linked. <a href="{{ discordAuthURL }}">Link Discord account here</a></p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -156,20 +156,32 @@
|
||||
</div>
|
||||
|
||||
|
||||
{{#if success}}
|
||||
{{#if success_message}}
|
||||
<div class="banner-notice success">
|
||||
<div>
|
||||
<p>{{success}}</p>
|
||||
<p>{{success_message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if error}}
|
||||
{{#if error_message}}
|
||||
<div class="banner-notice error">
|
||||
<div>
|
||||
<p>{{error}}</p>
|
||||
<p>{{error_message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="online-files-modal-wrapper hidden">
|
||||
<div class="online-files-modal">
|
||||
<h1 class="title dot">Password</h1>
|
||||
<p class="online-files-modal-caption">Enter your PNID password to download Cemu files</p>
|
||||
<input name="password" id="password" type="password" required>
|
||||
<div class="online-files-modal-button-wrapper">
|
||||
<button class="cancel" id="onlineFilesCloseButton">Cancel</button>
|
||||
<button class="confirm" id="onlineFilesConfirmButton">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/account.js"></script>
|
||||
@@ -1,6 +1,5 @@
|
||||
<link rel="stylesheet" href="/assets/css/upgrade.css" />
|
||||
<div class="wrapper">
|
||||
|
||||
<a href="/account" class="back-arrow">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
||||
<span>Back</span>
|
||||
|
||||
Reference in New Issue
Block a user