From b6a7b4084063c12778dfe6481b8981cb5b978ad8 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 10 Jul 2022 09:33:19 -0400 Subject: [PATCH 01/22] Moved redirect check to middleware --- src/middleware/redirect.js | 9 +++++++++ src/routers/account.js | 14 ++------------ src/server.js | 37 ++++++++++++++++++------------------- 3 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 src/middleware/redirect.js diff --git a/src/middleware/redirect.js b/src/middleware/redirect.js new file mode 100644 index 0000000..ce7f653 --- /dev/null +++ b/src/middleware/redirect.js @@ -0,0 +1,9 @@ +async function redirectMiddleware(request, response, next) { + if (request.method === 'POST') { + request.redirect = request.body.redirect?.startsWith('/') ? request.body.redirect : null; + } + + return next(); +} + +module.exports = redirectMiddleware; \ No newline at end of file diff --git a/src/routers/account.js b/src/routers/account.js index 202ebd5..52502b2 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -220,7 +220,6 @@ router.get('/login', async (request, response) => { router.post('/login', async (request, response) => { const { username, password } = request.body; - let { redirect } = request.body; let apiResponse = await util.apiPostGetRequest('/v1/login', {}, { username, @@ -255,11 +254,7 @@ router.post('/login', async (request, response) => { response.cookie('ph', encryptedBody.toString('hex'), { domain: '.pretendo.network' }); - if (!redirect.startsWith('/')) { - redirect = null; - } - - response.redirect(redirect || '/account'); + response.redirect(request.redirect || '/account'); }); router.get('/register', async (request, response) => { @@ -290,7 +285,6 @@ router.get('/register', async (request, response) => { 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' }); @@ -316,11 +310,7 @@ router.post('/register', async (request, response) => { response.clearCookie('username', { domain: '.pretendo.network' }); response.clearCookie('mii_name', { domain: '.pretendo.network' }); - if (!redirect.startsWith('/')) { - redirect = null; - } - - response.redirect(redirect || '/account'); + response.redirect(request.redirect || '/account'); }); router.get('/logout', async(_request, response) => { diff --git a/src/server.js b/src/server.js index 4e42444..d4b344b 100644 --- a/src/server.js +++ b/src/server.js @@ -6,9 +6,10 @@ 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 database = require('./database'); const util = require('./util'); +const logger = require('./logger'); const config = require('../config.json'); const defaultLocale = require('../locales/US_en.json'); @@ -19,25 +20,7 @@ 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(cookieParser()); - -// Locale express middleware setup app.use(expressLocale({ 'priority': ['cookie', 'accept-language', 'map', 'default'], cookie: { name: 'preferredLocale' }, @@ -82,6 +65,22 @@ app.use(expressLocale({ ], 'default': 'en-US' })); +app.use(redirectMiddleware); + +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('/', routers.home); app.use('/faq', routers.faq); From 13c8c24efe9b51c13c7bacc0c370a84fbfbae490 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 10 Jul 2022 10:27:57 -0400 Subject: [PATCH 02/22] locale checking moved to middleware --- src/middleware/locale.js | 13 +++++++++++++ src/routers/account.js | 14 -------------- src/routers/aprilfools.js | 4 +--- src/routers/blog.js | 4 ---- src/routers/docs.js | 8 ++------ src/routers/home.js | 6 ++---- src/routers/localization.js | 4 +--- src/routers/progress.js | 4 +--- src/server.js | 2 ++ 9 files changed, 22 insertions(+), 37 deletions(-) create mode 100644 src/middleware/locale.js diff --git a/src/middleware/locale.js b/src/middleware/locale.js new file mode 100644 index 0000000..8843caf --- /dev/null +++ b/src/middleware/locale.js @@ -0,0 +1,13 @@ +const util = require('../util'); + +async function localeMiddleware(request, response, next) { + const reqLocale = request.locale; + const locale = util.getLocale(reqLocale.region, reqLocale.language); + + response.locals.locale = locale; + response.locals.localeString = reqLocale.toString(); + + return next(); +} + +module.exports = localeMiddleware; \ No newline at end of file diff --git a/src/routers/account.js b/src/routers/account.js index 52502b2..adacc2c 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -33,8 +33,6 @@ router.get('/', async (request, response) => { // 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 }; @@ -206,8 +204,6 @@ router.get('/', async (request, response) => { 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 }; @@ -260,8 +256,6 @@ router.post('/login', async (request, response) => { 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, @@ -480,10 +474,6 @@ router.get('/online-files', async (request, response) => { }); 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'; @@ -530,8 +520,6 @@ router.get('/miieditor', async (request, response) => { response.render('account/miieditor', { layout: 'main', - locale, - localeString: reqLocale.toString(), encodedUserMiiData, editorToHex }); @@ -583,8 +571,6 @@ router.get('/upgrade', async (request, response) => { 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() diff --git a/src/routers/aprilfools.js b/src/routers/aprilfools.js index 818070f..394bc96 100644 --- a/src/routers/aprilfools.js +++ b/src/routers/aprilfools.js @@ -5,9 +5,7 @@ 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(), + layout: 'main' }; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; diff --git a/src/routers/blog.js b/src/routers/blog.js index cf1528a..9a3fee6 100644 --- a/src/routers/blog.js +++ b/src/routers/blog.js @@ -38,8 +38,6 @@ 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, }; @@ -74,8 +72,6 @@ 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, }; diff --git a/src/routers/docs.js b/src/routers/docs.js index 56ae3ca..ca2bb15 100644 --- a/src/routers/docs.js +++ b/src/routers/docs.js @@ -13,8 +13,6 @@ 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, }; @@ -31,8 +29,6 @@ router.get('/search', async (request, response) => { 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, }; @@ -46,10 +42,10 @@ router.get('/:slug', async (request, response, next) => { // 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'; diff --git a/src/routers/home.js b/src/routers/home.js index f2f61e0..05a17e7 100644 --- a/src/routers/home.js +++ b/src/routers/home.js @@ -9,9 +9,7 @@ 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; @@ -26,7 +24,7 @@ router.get('/', async (request, response) => { // 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)); diff --git a/src/routers/localization.js b/src/routers/localization.js index c4fee3c..f6178f2 100644 --- a/src/routers/localization.js +++ b/src/routers/localization.js @@ -4,9 +4,7 @@ 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(), + layout: 'main' }; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; diff --git a/src/routers/progress.js b/src/routers/progress.js index 4849321..c1edb92 100644 --- a/src/routers/progress.js +++ b/src/routers/progress.js @@ -8,9 +8,7 @@ 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; diff --git a/src/server.js b/src/server.js index d4b344b..b7a2d00 100644 --- a/src/server.js +++ b/src/server.js @@ -7,6 +7,7 @@ const expressLocale = require('express-locale'); const cookieParser = require('cookie-parser'); const Stripe = require('stripe'); const redirectMiddleware = require('./middleware/redirect'); +const localeMiddleware = require('./middleware/locale'); const database = require('./database'); const util = require('./util'); const logger = require('./logger'); @@ -66,6 +67,7 @@ app.use(expressLocale({ 'default': 'en-US' })); app.use(redirectMiddleware); +app.use(localeMiddleware); logger.info('Setting up static public folder'); app.use(express.static('public')); From 9e996278391d9badcac16e3d2aedf4ea8edb4c5c Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 10 Jul 2022 10:30:40 -0400 Subject: [PATCH 03/22] Moved redirect query param check to middleware --- src/middleware/redirect.js | 4 ++++ src/routers/account.js | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/middleware/redirect.js b/src/middleware/redirect.js index ce7f653..7ca4fb7 100644 --- a/src/middleware/redirect.js +++ b/src/middleware/redirect.js @@ -3,6 +3,10 @@ async function redirectMiddleware(request, response, next) { 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(); } diff --git a/src/routers/account.js b/src/routers/account.js index adacc2c..10f4423 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -207,8 +207,6 @@ router.get('/login', async (request, response) => { error: request.cookies.error }; - renderData.redirect = request.query.redirect; - response.clearCookie('error', { domain: '.pretendo.network' }); response.render('account/login', renderData); @@ -267,13 +265,6 @@ router.get('/register', async (request, response) => { 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); }); From 0696dddaaf65efedc7e536c6edaaa9d2a0665579 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 10 Jul 2022 10:33:03 -0400 Subject: [PATCH 04/22] handlebars looks for main layout by default --- src/routers/account.js | 5 ----- src/routers/aprilfools.js | 4 +--- src/routers/blog.js | 3 +-- src/routers/docs.js | 6 ++---- src/routers/home.js | 1 - src/routers/localization.js | 4 +--- src/routers/progress.js | 1 - 7 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/routers/account.js b/src/routers/account.js index 10f4423..61bf36d 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -32,7 +32,6 @@ router.get('/', async (request, response) => { // Setup the data to be sent to the handlebars renderer const renderData = { - layout: 'main', success: request.cookies.success, error: request.cookies.error }; @@ -203,7 +202,6 @@ router.get('/', async (request, response) => { router.get('/login', async (request, response) => { const renderData = { - layout: 'main', error: request.cookies.error }; @@ -253,7 +251,6 @@ router.post('/login', async (request, response) => { router.get('/register', async (request, response) => { const renderData = { - layout: 'main', error: request.cookies.error, email: request.cookies.email, username: request.cookies.username, @@ -510,7 +507,6 @@ router.get('/miieditor', async (request, response) => { }; response.render('account/miieditor', { - layout: 'main', encodedUserMiiData, editorToHex }); @@ -561,7 +557,6 @@ router.get('/upgrade', async (request, response) => { const pnid = await database.PNID.findOne({ pid }); const renderData = { - layout: 'main', error: request.cookies.error, currentTier: pnid.get('connections.stripe.price_id'), donationCache: await cache.getStripeDonationCache() diff --git a/src/routers/aprilfools.js b/src/routers/aprilfools.js index 394bc96..0cbdcf2 100644 --- a/src/routers/aprilfools.js +++ b/src/routers/aprilfools.js @@ -4,9 +4,7 @@ const router = new Router(); router.get('/', async (request, response) => { - const renderData = { - layout: 'main' - }; + const renderData = {}; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; diff --git a/src/routers/blog.js b/src/routers/blog.js index 9a3fee6..78823f5 100644 --- a/src/routers/blog.js +++ b/src/routers/blog.js @@ -37,8 +37,7 @@ const postList = () => { router.get('/', async (request, response) => { const renderData = { - layout: 'main', - postList, + postList }; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; diff --git a/src/routers/docs.js b/src/routers/docs.js index ca2bb15..2002ae8 100644 --- a/src/routers/docs.js +++ b/src/routers/docs.js @@ -12,8 +12,7 @@ router.get('/', async (request, response) => { router.get('/search', async (request, response) => { const renderData = { - layout: 'main', - currentPage: request.params.slug, + currentPage: request.params.slug }; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; @@ -28,8 +27,7 @@ router.get('/search', async (request, response) => { router.get('/:slug', async (request, response, next) => { const renderData = { - layout: 'main', - currentPage: request.params.slug, + currentPage: request.params.slug }; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; diff --git a/src/routers/home.js b/src/routers/home.js index 05a17e7..66cd61e 100644 --- a/src/routers/home.js +++ b/src/routers/home.js @@ -8,7 +8,6 @@ const { getTrelloCache } = require('../cache'); router.get('/', async (request, response) => { const renderData = { - layout: 'main', boards }; diff --git a/src/routers/localization.js b/src/routers/localization.js index f6178f2..598ffcc 100644 --- a/src/routers/localization.js +++ b/src/routers/localization.js @@ -3,9 +3,7 @@ const util = require('../util'); const router = new Router(); router.get('/', async (request, response) => { - const renderData = { - layout: 'main' - }; + const renderData = {}; renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; diff --git a/src/routers/progress.js b/src/routers/progress.js index c1edb92..ebbaf2a 100644 --- a/src/routers/progress.js +++ b/src/routers/progress.js @@ -7,7 +7,6 @@ const { getTrelloCache, getStripeDonationCache } = require('../cache'); router.get('/', async (request, response) => { const renderData = { - layout: 'main', boards }; From 02da4c10cd1d629d4086597de6bec473c125d88a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 10 Jul 2022 12:22:15 -0400 Subject: [PATCH 05/22] Moved cookie messages to middleware --- src/middleware/locale.js | 13 ----------- src/middleware/render-data.js | 22 +++++++++++++++++++ src/routers/account.js | 37 +++++++++++--------------------- src/server.js | 4 ++-- views/account/account.handlebars | 8 +++---- 5 files changed, 40 insertions(+), 44 deletions(-) delete mode 100644 src/middleware/locale.js create mode 100644 src/middleware/render-data.js diff --git a/src/middleware/locale.js b/src/middleware/locale.js deleted file mode 100644 index 8843caf..0000000 --- a/src/middleware/locale.js +++ /dev/null @@ -1,13 +0,0 @@ -const util = require('../util'); - -async function localeMiddleware(request, response, next) { - const reqLocale = request.locale; - const locale = util.getLocale(reqLocale.region, reqLocale.language); - - response.locals.locale = locale; - response.locals.localeString = reqLocale.toString(); - - return next(); -} - -module.exports = localeMiddleware; \ No newline at end of file diff --git a/src/middleware/render-data.js b/src/middleware/render-data.js new file mode 100644 index 0000000..8e2a384 --- /dev/null +++ b/src/middleware/render-data.js @@ -0,0 +1,22 @@ +const util = require('../util'); + +async function renderDataMiddleware(request, response, 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' }); + + return next(); +} + +module.exports = renderDataMiddleware; \ No newline at end of file diff --git a/src/routers/account.js b/src/routers/account.js index 61bf36d..0918d67 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -31,14 +31,7 @@ router.get('/', async (request, response) => { } // Setup the data to be sent to the handlebars renderer - const renderData = { - success: request.cookies.success, - error: request.cookies.error - }; - - // Reset message cookies - response.clearCookie('success', { domain: '.pretendo.network' }); - response.clearCookie('error', { domain: '.pretendo.network' }); + const renderData = {}; // Check for Stripe messages const { upgrade_success } = request.query; @@ -201,13 +194,7 @@ router.get('/', async (request, response) => { }); router.get('/login', async (request, response) => { - const renderData = { - error: request.cookies.error - }; - - response.clearCookie('error', { domain: '.pretendo.network' }); - - response.render('account/login', renderData); + response.render('account/login'); }); router.post('/login', async (request, response) => { @@ -220,7 +207,7 @@ router.post('/login', async (request, response) => { }); if (apiResponse.statusCode !== 200) { - response.cookie('error', apiResponse.body.error, { domain: '.pretendo.network' }); + response.cookie('error_message', apiResponse.body.error, { domain: '.pretendo.network' }); return response.redirect('/account/login'); } @@ -251,13 +238,11 @@ router.post('/login', async (request, response) => { router.get('/register', async (request, response) => { const renderData = { - 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' }); @@ -277,7 +262,7 @@ router.post('/register', async (request, response) => { }); if (apiResponse.statusCode !== 200) { - response.cookie('error', apiResponse.body.error, { domain: '.pretendo.network' }); + response.cookie('error_message', apiResponse.body.error, { domain: '.pretendo.network' }); return response.redirect('/account/register'); } @@ -287,7 +272,6 @@ router.post('/register', async (request, response) => { 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' }); @@ -314,7 +298,7 @@ router.get('/connect/discord', async (request, response) => { grantType: 'authorization_code', }); } catch (error) { - response.cookie('error', 'Invalid Discord authorization code. Please try again', { domain: '.pretendo.network' }); + response.cookie('error_message', 'Invalid Discord authorization code. Please try again', { domain: '.pretendo.network' }); return response.redirect('/account'); } @@ -371,7 +355,9 @@ router.get('/connect/discord', async (request, response) => { } } - response.cookie('success', 'Discord account linked successfully', { domain: '.pretendo.network' }).redirect('/account'); + response.cookie('success_message', 'Discord account linked successfully', { domain: '.pretendo.network' }); + + response.redirect('/account'); }); router.get('/online-files', async (request, response) => { @@ -662,7 +648,7 @@ router.post('/stripe/checkout/:priceId', async (request, response) => { 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' }); + response.cookie('error_message', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' }); return response.redirect('/account'); } @@ -684,7 +670,8 @@ router.post('/stripe/checkout/:priceId', async (request, response) => { } catch (error) { // Maybe we need a dedicated error page? // Or handle this as not cookies? - response.cookie('error', error.message, { domain: '.pretendo.network' }); + response.cookie('error_message', error.message, { domain: '.pretendo.network' }); + return response.redirect('/account'); } }); @@ -756,7 +743,7 @@ router.post('/stripe/unsubscribe', async (request, response) => { } 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' }); + response.cookie('error_message', 'Error canceling subscription! Contact support if issue persists', { domain: '.pretendo.network' }); return response.redirect('/account'); } diff --git a/src/server.js b/src/server.js index b7a2d00..cfa0735 100644 --- a/src/server.js +++ b/src/server.js @@ -7,7 +7,7 @@ const expressLocale = require('express-locale'); const cookieParser = require('cookie-parser'); const Stripe = require('stripe'); const redirectMiddleware = require('./middleware/redirect'); -const localeMiddleware = require('./middleware/locale'); +const renderDataMiddleware = require('./middleware/render-data'); const database = require('./database'); const util = require('./util'); const logger = require('./logger'); @@ -67,7 +67,7 @@ app.use(expressLocale({ 'default': 'en-US' })); app.use(redirectMiddleware); -app.use(localeMiddleware); +app.use(renderDataMiddleware); logger.info('Setting up static public folder'); app.use(express.static('public')); diff --git a/views/account/account.handlebars b/views/account/account.handlebars index 35d05ab..d530e91 100644 --- a/views/account/account.handlebars +++ b/views/account/account.handlebars @@ -156,18 +156,18 @@ -{{#if success}} +{{#if success_message}} {{/if}} -{{#if error}} +{{#if error_message}} {{/if}} From e9175f491d422dd3ebe33bafea6c300c3a4dcfae Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 10 Jul 2022 12:32:57 -0400 Subject: [PATCH 06/22] Fixed scroll issues with modal open --- public/assets/css/upgrade.css | 5 ++++- public/assets/js/upgrade.js | 4 ++++ views/account/upgrade.handlebars | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/public/assets/css/upgrade.css b/public/assets/css/upgrade.css index 2e0229d..87885ab 100644 --- a/public/assets/css/upgrade.css +++ b/public/assets/css/upgrade.css @@ -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%; diff --git a/public/assets/js/upgrade.js b/public/assets/js/upgrade.js index a652d6a..bc6b451 100644 --- a/public/assets/js/upgrade.js +++ b/public/assets/js/upgrade.js @@ -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) { diff --git a/views/account/upgrade.handlebars b/views/account/upgrade.handlebars index 4c5b2fd..9cca93a 100644 --- a/views/account/upgrade.handlebars +++ b/views/account/upgrade.handlebars @@ -1,6 +1,5 @@
- Back From 6bec0fdc31ce75cd7f3956dcf858fd5df1789696 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 15 Jul 2022 20:48:37 -0400 Subject: [PATCH 07/22] Simpler Discord queries --- package-lock.json | 117 +++++++++++++++++++++++++++++++ package.json | 2 + public/assets/js/account.js | 2 +- src/routers/account.js | 100 +++----------------------- src/schema/pnid.js | 3 + views/account/account.handlebars | 2 +- 6 files changed, 134 insertions(+), 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index 681e34c..cc64560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 590c825..a7769cb 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/assets/js/account.js b/public/assets/js/account.js index 2e6da46..1ca1b5d 100644 --- a/public/assets/js/account.js +++ b/public/assets/js/account.js @@ -5,7 +5,7 @@ document.getElementById('remove-discord-connection')?.addEventListener('click', const tokenType = document.cookie.split('; ').find(row => row.startsWith('token_type=')).split('=')[1]; const accessToken = document.cookie.split('; ').find(row => row.startsWith('access_token=')).split('=')[1]; - fetch('https://api.pretendo.cc/v1/connections/remove/discord', { + fetch('http://api.pretendo.cc/v1/connections/remove/discord', { method: 'DELETE', headers: { 'Content-Type': 'application/json', diff --git a/src/routers/account.js b/src/routers/account.js index 0918d67..d42617a 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -4,6 +4,8 @@ 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 database = require('../database'); const cache = require('../cache'); const util = require('../util'); @@ -15,13 +17,14 @@ const aesKey = Buffer.from(config.aes_key, 'hex'); 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: 'v9' + version: 'v10' }); router.get('/', async (request, response) => { @@ -94,89 +97,10 @@ router.get('/', async (request, response) => { // 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); + renderData.discordUser = await discordRest.get(DiscordRoutes.user(account.connections.discord.id)); } 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)); + response.cookie('error_message', error.message, { domain: '.pretendo.network' }); } } else { // If no Discord account linked, generate an auth URL @@ -310,9 +234,7 @@ router.get('/connect/discord', async (request, response) => { 'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}` }, { data: { - id: user.id, - access_token: tokens.access_token, - refresh_token: tokens.refresh_token, + id: user.id // Only care about user ID. Bot will get details we need } }); @@ -340,9 +262,7 @@ router.get('/connect/discord', async (request, response) => { 'Authorization': `${tokens.token_type} ${tokens.access_token}` }, { data: { - id: user.id, - access_token: tokens.access_token, - refresh_token: tokens.refresh_token, + id: user.id } }); @@ -648,8 +568,8 @@ router.post('/stripe/checkout/:priceId', async (request, response) => { 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'); + //response.cookie('error_message', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' }); + //return response.redirect('/account'); } try { diff --git a/src/schema/pnid.js b/src/schema/pnid.js index a488521..63a2c47 100644 --- a/src/schema/pnid.js +++ b/src/schema/pnid.js @@ -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, diff --git a/views/account/account.handlebars b/views/account/account.handlebars index d530e91..33fb3db 100644 --- a/views/account/account.handlebars +++ b/views/account/account.handlebars @@ -139,7 +139,7 @@

Connected to Discord as {{ discordUser.username }}#{{ discordUser.discriminator }}

{{else}} -

To link your Discord account, run /whatever in the Discord server

+

No Discord account linked. Link Discord account here

{{/if}}
From 1fe69f915f75acc5d3e6d9d3deeba503d3b74b8a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 15 Jul 2022 20:49:52 -0400 Subject: [PATCH 08/22] default latest_webhook_timestamp value --- src/routers/account.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routers/account.js b/src/routers/account.js index d42617a..1ee44e9 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -559,7 +559,8 @@ router.post('/stripe/checkout/:priceId', async (request, response) => { await database.PNID.updateOne({ pid }, { $set: { - 'connections.stripe.customer_id': customer.id // ensure PNID always has latest customer ID + 'connections.stripe.customer_id': customer.id, // ensure PNID always has latest customer ID + 'connections.stripe.latest_webhook_timestamp': 0 } }, { upsert: true }).exec(); From 868bc1364a03ac42d568b09bd665f4cd70e87cd6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 15 Jul 2022 20:52:21 -0400 Subject: [PATCH 09/22] Fixed tier success/error messages --- src/routers/account.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routers/account.js b/src/routers/account.js index 1ee44e9..0adfc0f 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -40,9 +40,9 @@ router.get('/', async (request, response) => { const { upgrade_success } = request.query; if (upgrade_success === 'true') { - renderData.success = 'Account upgraded successfully'; + renderData.success_message = 'Account upgraded successfully'; } else if (upgrade_success === 'false') { - renderData.error = 'Account upgrade failed'; + renderData.error_message = 'Account upgrade failed'; } // Attempt to get user data From cb4f9f8c35b1f44decca9dba121663dc40d2b2b5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 15 Jul 2022 21:09:20 -0400 Subject: [PATCH 10/22] Moved login check to middleware --- src/middleware/pnid.js | 54 ++++++++++ src/routers/account.js | 232 +++-------------------------------------- 2 files changed, 68 insertions(+), 218 deletions(-) create mode 100644 src/middleware/pnid.js diff --git a/src/middleware/pnid.js b/src/middleware/pnid.js new file mode 100644 index 0000000..b91482c --- /dev/null +++ b/src/middleware/pnid.js @@ -0,0 +1,54 @@ +const util = require('../util'); +const database = require('../database'); + +async function pnidMiddleware(request, response, next) { + // Verify the user is logged in + if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) { + return response.redirect(`/account/login?redirect=${request.originalUrl}`); + } + + // 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' + }); + } + + request.account = apiResponse.body; + request.pnid = await database.PNID.findOne({ pid: request.account.pid }); + + return next(); +} + +module.exports = pnidMiddleware; \ No newline at end of file diff --git a/src/routers/account.js b/src/routers/account.js index 0adfc0f..abe167f 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -6,6 +6,7 @@ const AdmZip = require('adm-zip'); const Stripe = require('stripe'); const { REST: DiscordRest } = require('@discordjs/rest'); const { Routes: DiscordRoutes } = require('discord-api-types/v10'); +const pnidMiddleware = require('../middleware/pnid'); const database = require('../database'); const cache = require('../cache'); const util = require('../util'); @@ -27,12 +28,7 @@ const discordOAuth = new DiscordOauth2({ version: 'v10' }); -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'); - } - +router.get('/', pnidMiddleware, async (request, response) => { // Setup the data to be sent to the handlebars renderer const renderData = {}; @@ -45,49 +41,8 @@ router.get('/', async (request, response) => { renderData.error_message = '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 }); + const { account } = request; + const { pnid } = request; renderData.tierName = pnid.get('connections.stripe.tier_name'); renderData.tierLevel = pnid.get('connections.stripe.tier_level'); @@ -212,7 +167,7 @@ router.get('/logout', async(_request, response) => { response.redirect('/'); }); -router.get('/connect/discord', async (request, response) => { +router.get('/connect/discord', pnidMiddleware, async (request, response) => { let tokens; try { // Attempt to get OAuth2 tokens @@ -280,52 +235,8 @@ router.get('/connect/discord', async (request, response) => { response.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; +router.get('/online-files', pnidMiddleware, async (request, response) => { + const { account } = request; const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, Buffer.alloc(16)); @@ -418,49 +329,9 @@ router.get('/miieditor', async (request, response) => { }); }); -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'); - } - +router.get('/upgrade', pnidMiddleware, async (request, response) => { // Set user account info to render data - const account = apiResponse.body; - const pid = account.pid; - - const pnid = await database.PNID.findOne({ pid }); + const { pnid } = request; const renderData = { error: request.cookies.error, @@ -499,46 +370,9 @@ router.get('/upgrade', async (request, response) => { 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'); - } - +router.post('/stripe/checkout/:priceId', pnidMiddleware, async (request, response) => { // Set user account info to render data - const account = apiResponse.body; + const { account } = request; const pid = account.pid; let customer; @@ -597,49 +431,11 @@ router.post('/stripe/checkout/:priceId', async (request, response) => { } }); -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'); - } - +router.post('/stripe/unsubscribe', pnidMiddleware, async (request, response) => { // Set user account info to render data - const account = apiResponse.body; - const pid = account.pid; + const { pnid } = request; - const pnid = await database.PNID.findOne({ pid }); + const pid = pnid.get('pid'); const subscriptionId = pnid.get('connections.stripe.subscription_id'); const tierName = pnid.get('connections.stripe.tier_name'); From 512173b47040ab2ae15dd1d0a4d58c801db865b7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 07:02:51 -0400 Subject: [PATCH 11/22] routers -> routes --- src/{routers => routes}/account.js | 0 src/{routers => routes}/aprilfools.js | 0 src/{routers => routes}/blog.js | 0 src/{routers => routes}/docs.js | 0 src/{routers => routes}/faq.js | 0 src/{routers => routes}/home.js | 0 src/{routers => routes}/localization.js | 0 src/{routers => routes}/progress.js | 0 src/server.js | 36 ++++++++++++------------- 9 files changed, 18 insertions(+), 18 deletions(-) rename src/{routers => routes}/account.js (100%) rename src/{routers => routes}/aprilfools.js (100%) rename src/{routers => routes}/blog.js (100%) rename src/{routers => routes}/docs.js (100%) rename src/{routers => routes}/faq.js (100%) rename src/{routers => routes}/home.js (100%) rename src/{routers => routes}/localization.js (100%) rename src/{routers => routes}/progress.js (100%) diff --git a/src/routers/account.js b/src/routes/account.js similarity index 100% rename from src/routers/account.js rename to src/routes/account.js diff --git a/src/routers/aprilfools.js b/src/routes/aprilfools.js similarity index 100% rename from src/routers/aprilfools.js rename to src/routes/aprilfools.js diff --git a/src/routers/blog.js b/src/routes/blog.js similarity index 100% rename from src/routers/blog.js rename to src/routes/blog.js diff --git a/src/routers/docs.js b/src/routes/docs.js similarity index 100% rename from src/routers/docs.js rename to src/routes/docs.js diff --git a/src/routers/faq.js b/src/routes/faq.js similarity index 100% rename from src/routers/faq.js rename to src/routes/faq.js diff --git a/src/routers/home.js b/src/routes/home.js similarity index 100% rename from src/routers/home.js rename to src/routes/home.js diff --git a/src/routers/localization.js b/src/routes/localization.js similarity index 100% rename from src/routers/localization.js rename to src/routes/localization.js diff --git a/src/routers/progress.js b/src/routes/progress.js similarity index 100% rename from src/routers/progress.js rename to src/routes/progress.js diff --git a/src/server.js b/src/server.js index cfa0735..48edf33 100644 --- a/src/server.js +++ b/src/server.js @@ -72,26 +72,26 @@ app.use(renderDataMiddleware); 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') +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('/', 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); +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 From ecb07e7b020c60dfc7ea7b94f0f85657f60836dc Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 08:02:04 -0400 Subject: [PATCH 12/22] Moved API requests out of route handlers --- src/middleware/pnid.js | 47 ++----------- src/routes/account.js | 147 ++++++++++++++--------------------------- src/util.js | 85 ++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 136 deletions(-) diff --git a/src/middleware/pnid.js b/src/middleware/pnid.js index b91482c..39a7382 100644 --- a/src/middleware/pnid.js +++ b/src/middleware/pnid.js @@ -7,48 +7,15 @@ async function pnidMiddleware(request, response, next) { return response.redirect(`/account/login?redirect=${request.originalUrl}`); } - // Attempt to get user data - let apiResponse = await util.apiGetRequest('/v1/user', { - 'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}` - }); + try { + request.account = await util.getUserAccountData(request, response); + request.pnid = await database.PNID.findOne({ pid: request.account.pid }); - 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}` - }); + return next(); + } catch (error) { + response.cookie('error_message', error.message, { domain: '.pretendo.network' }); + return response.redirect('/account/login'); } - - // If still failed, something went horribly wrong - if (apiResponse.statusCode !== 200) { - // TODO: Error message - return response.status(apiResponse.statusCode).json({ - error: 'Bad' - }); - } - - request.account = apiResponse.body; - request.pnid = await database.PNID.findOne({ pid: request.account.pid }); - - return next(); } module.exports = pnidMiddleware; \ No newline at end of file diff --git a/src/routes/account.js b/src/routes/account.js index abe167f..534e55c 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -79,40 +79,32 @@ router.get('/login', async (request, response) => { router.post('/login', async (request, response) => { const { username, password } = request.body; - let apiResponse = await util.apiPostGetRequest('/v1/login', {}, { - username, - password, - grant_type: 'password' - }); + try { + const tokens = await util.login(username, password); - if (apiResponse.statusCode !== 200) { - response.cookie('error_message', apiResponse.body.error, { domain: '.pretendo.network' }); + 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' }); + + const account = await util.getUserAccountData(request, response); + + 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' }); + + response.redirect(request.redirect || '/account'); + + } catch (error) { + console.log(error); + response.cookie('error_message', error.message, { 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' }); - - response.redirect(request.redirect || '/account'); }); router.get('/register', async (request, response) => { @@ -136,26 +128,29 @@ router.post('/register', async (request, response) => { 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 - }); + try { + const tokens = await util.register({ + email, + username, + mii_name, + password, + password_confirm, + hCaptchaResponse + }); - if (apiResponse.statusCode !== 200) { - response.cookie('error_message', apiResponse.body.error, { domain: '.pretendo.network' }); + 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'); } - - 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('email', { domain: '.pretendo.network' }); - response.clearCookie('username', { domain: '.pretendo.network' }); - response.clearCookie('mii_name', { domain: '.pretendo.network' }); - - response.redirect(request.redirect || '/account'); }); router.get('/logout', async(_request, response) => { @@ -182,57 +177,17 @@ router.get('/connect/discord', pnidMiddleware, async (request, response) => { } // Get Discord user data - const user = await discordOAuth.getUser(tokens.access_token); + const discordUser = 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 // Only care about user ID. Bot will get details we need - } - }); + try { + await util.updateDiscordConnection(discordUser, request, response); - 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 - } - }); - - // 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_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'); } - - response.cookie('success_message', 'Discord account linked successfully', { domain: '.pretendo.network' }); - - response.redirect('/account'); }); router.get('/online-files', pnidMiddleware, async (request, response) => { diff --git a/src/util.js b/src/util.js index 0394c6c..d7a561a 100644 --- a/src/util.js +++ b/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); @@ -301,6 +381,11 @@ module.exports = { apiGetRequest, apiPostGetRequest, apiDeleteGetRequest, + register, + login, + refreshLogin, + getUserAccountData, + updateDiscordConnection, nintendoPasswordHash, handleStripeEvent, getAccount From 12d0a9206eeeb327eaad67eff502c59089f2b047 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 09:44:28 -0400 Subject: [PATCH 13/22] Removed ph cookie, users now supply password for online files --- example.config.json | 3 +- public/assets/css/account.css | 97 ++++++++++++++++++++++++++++++++ public/assets/js/account.js | 40 +++++++++++++ src/middleware/pnid.js | 2 +- src/routes/account.js | 30 +++------- src/routes/aprilfools.js | 2 +- src/routes/blog.js | 4 +- src/routes/docs.js | 4 +- src/routes/home.js | 2 +- src/routes/localization.js | 2 +- src/routes/progress.js | 2 +- src/server.js | 5 +- views/account/account.handlebars | 12 ++++ 13 files changed, 170 insertions(+), 35 deletions(-) diff --git a/example.config.json b/example.config.json index 4987193..2949df2 100644 --- a/example.config.json +++ b/example.config.json @@ -34,6 +34,5 @@ "gmail": { "user": "email@gmail.com", "pass": "app-password" - }, - "aes_key": "hex key here" + } } \ No newline at end of file diff --git a/public/assets/css/account.css b/public/assets/css/account.css index 1b5a6b2..715076d 100644 --- a/public/assets/css/account.css +++ b/public/assets/css/account.css @@ -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; } diff --git a/public/assets/js/account.js b/public/assets/js/account.js index 1ca1b5d..b1b0435 100644 --- a/public/assets/js/account.js +++ b/public/assets/js/account.js @@ -19,4 +19,44 @@ 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() + .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'); }); \ No newline at end of file diff --git a/src/middleware/pnid.js b/src/middleware/pnid.js index 39a7382..507da57 100644 --- a/src/middleware/pnid.js +++ b/src/middleware/pnid.js @@ -3,7 +3,7 @@ const database = require('../database'); async function pnidMiddleware(request, response, next) { // Verify the user is logged in - if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) { + if (!request.cookies.access_token || !request.cookies.refresh_token) { return response.redirect(`/account/login?redirect=${request.originalUrl}`); } diff --git a/src/routes/account.js b/src/routes/account.js index 534e55c..c80799a 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -14,7 +14,6 @@ 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(); @@ -48,7 +47,7 @@ router.get('/', pnidMiddleware, async (request, response) => { 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; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; // Check if a Discord account is linked to the PNID if (account.connections.discord.id && account.connections.discord.id.trim() !== '') { @@ -67,7 +66,7 @@ router.get('/', pnidMiddleware, async (request, response) => { renderData.discordAuthURL = discordAuthURL; } - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; response.render('account/account', renderData); }); @@ -86,18 +85,6 @@ router.post('/login', async (request, response) => { response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' }); response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' }); - const account = await util.getUserAccountData(request, response); - - 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' }); - response.redirect(request.redirect || '/account'); } catch (error) { @@ -157,7 +144,6 @@ 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('/'); }); @@ -190,13 +176,11 @@ router.get('/connect/discord', pnidMiddleware, async (request, response) => { } }); -router.get('/online-files', pnidMiddleware, async (request, response) => { +router.post('/online-files', pnidMiddleware, async (request, response) => { const { account } = request; + const { password } = request.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 hashedPassword = util.nintendoPasswordHash(password, account.pid); const miiNameBuffer = Buffer.alloc(0x16); const miiName = Buffer.from(account.mii.name, 'utf16le').swap16(); @@ -218,7 +202,7 @@ router.get('/online-files', pnidMiddleware, async (request, response) => { accountDat += 'SimpleAddressId=0\n'; accountDat += `PrincipalId=${account.pid.toString(16)}\n`; accountDat += 'IsPasswordCacheEnabled=1\n'; - accountDat += `AccountPasswordCache=${decryptedPasswordHash.toString('hex')}`; + accountDat += `AccountPasswordCache=${hashedPassword}`; const onlineFiles = new AdmZip(); @@ -227,7 +211,7 @@ router.get('/online-files', pnidMiddleware, async (request, response) => { onlineFiles.addFile('seeprom.bin', Buffer.alloc(0x200)); // nulled SEEPROM response.status(200); - response.set('Content-Disposition', 'attachment; filename="Online Files.zip'); + response.set('Content-Disposition', 'attachment; filename="Cemu Pretendo Online Files.zip'); response.set('Content-Type', 'application/zip'); response.end(onlineFiles.toBuffer()); diff --git a/src/routes/aprilfools.js b/src/routes/aprilfools.js index 0cbdcf2..1b69014 100644 --- a/src/routes/aprilfools.js +++ b/src/routes/aprilfools.js @@ -6,7 +6,7 @@ router.get('/', async (request, response) => { const renderData = {}; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); diff --git a/src/routes/blog.js b/src/routes/blog.js index 78823f5..749c314 100644 --- a/src/routes/blog.js +++ b/src/routes/blog.js @@ -40,7 +40,7 @@ router.get('/', async (request, response) => { postList }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); @@ -74,7 +74,7 @@ router.get('/:slug', async (request, response, next) => { postList, }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); diff --git a/src/routes/docs.js b/src/routes/docs.js index 2002ae8..913c27f 100644 --- a/src/routes/docs.js +++ b/src/routes/docs.js @@ -15,7 +15,7 @@ router.get('/search', async (request, response) => { currentPage: request.params.slug }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); @@ -30,7 +30,7 @@ router.get('/:slug', async (request, response, next) => { currentPage: request.params.slug }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); diff --git a/src/routes/home.js b/src/routes/home.js index 66cd61e..d0a94c5 100644 --- a/src/routes/home.js +++ b/src/routes/home.js @@ -11,7 +11,7 @@ router.get('/', async (request, response) => { boards }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); diff --git a/src/routes/localization.js b/src/routes/localization.js index 598ffcc..72819c0 100644 --- a/src/routes/localization.js +++ b/src/routes/localization.js @@ -5,7 +5,7 @@ const router = new Router(); router.get('/', async (request, response) => { const renderData = {}; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); diff --git a/src/routes/progress.js b/src/routes/progress.js index ebbaf2a..eacd849 100644 --- a/src/routes/progress.js +++ b/src/routes/progress.js @@ -10,7 +10,7 @@ router.get('/', async (request, response) => { boards }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; if (renderData.isLoggedIn) { const account = await util.getAccount(request, response); diff --git a/src/server.js b/src/server.js index 48edf33..5f0b094 100644 --- a/src/server.js +++ b/src/server.js @@ -20,7 +20,10 @@ const stripe = new Stripe(config.stripe.secret_key); logger.info('Setting up Middleware'); app.use(morgan('dev')); -app.use(express.urlencoded({ extended: true })); +app.use(express.json()); +app.use(express.urlencoded({ + extended: true +})); app.use(cookieParser()); app.use(expressLocale({ 'priority': ['cookie', 'accept-language', 'map', 'default'], diff --git a/views/account/account.handlebars b/views/account/account.handlebars index 33fb3db..70533a7 100644 --- a/views/account/account.handlebars +++ b/views/account/account.handlebars @@ -172,4 +172,16 @@ {{/if}} + + \ No newline at end of file From 25753009078e46d81b1e21147126386668e7268d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 09:49:23 -0400 Subject: [PATCH 14/22] removed duplicate isLoggedIn set --- src/routes/account.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes/account.js b/src/routes/account.js index c80799a..db6f432 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -66,8 +66,6 @@ router.get('/', pnidMiddleware, async (request, response) => { renderData.discordAuthURL = discordAuthURL; } - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - response.render('account/account', renderData); }); From 8d4c195ad45ec6fb9a29f8fb4d2d1f96c2c9a022 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 09:55:09 -0400 Subject: [PATCH 15/22] movedIsLoggedIn to render-data middleware --- src/middleware/render-data.js | 2 ++ src/routes/account.js | 1 - src/routes/aprilfools.js | 4 +--- src/routes/blog.js | 8 ++------ src/routes/docs.js | 8 ++------ src/routes/home.js | 4 +--- src/routes/localization.js | 4 +--- src/routes/progress.js | 4 +--- 8 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/middleware/render-data.js b/src/middleware/render-data.js index 8e2a384..8673377 100644 --- a/src/middleware/render-data.js +++ b/src/middleware/render-data.js @@ -15,6 +15,8 @@ async function renderDataMiddleware(request, response, next) { // 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; return next(); } diff --git a/src/routes/account.js b/src/routes/account.js index db6f432..771b712 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -47,7 +47,6 @@ router.get('/', pnidMiddleware, async (request, response) => { 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; // Check if a Discord account is linked to the PNID if (account.connections.discord.id && account.connections.discord.id.trim() !== '') { diff --git a/src/routes/aprilfools.js b/src/routes/aprilfools.js index 1b69014..74d7454 100644 --- a/src/routes/aprilfools.js +++ b/src/routes/aprilfools.js @@ -6,9 +6,7 @@ router.get('/', async (request, response) => { const renderData = {}; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } diff --git a/src/routes/blog.js b/src/routes/blog.js index 749c314..2b27e23 100644 --- a/src/routes/blog.js +++ b/src/routes/blog.js @@ -40,9 +40,7 @@ router.get('/', async (request, response) => { postList }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } @@ -74,9 +72,7 @@ router.get('/:slug', async (request, response, next) => { postList, }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } diff --git a/src/routes/docs.js b/src/routes/docs.js index 913c27f..5f6697a 100644 --- a/src/routes/docs.js +++ b/src/routes/docs.js @@ -15,9 +15,7 @@ router.get('/search', async (request, response) => { currentPage: request.params.slug }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } @@ -30,9 +28,7 @@ router.get('/:slug', async (request, response, next) => { currentPage: request.params.slug }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } diff --git a/src/routes/home.js b/src/routes/home.js index d0a94c5..fc21522 100644 --- a/src/routes/home.js +++ b/src/routes/home.js @@ -11,9 +11,7 @@ router.get('/', async (request, response) => { boards }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } diff --git a/src/routes/localization.js b/src/routes/localization.js index 72819c0..7e4dec3 100644 --- a/src/routes/localization.js +++ b/src/routes/localization.js @@ -5,9 +5,7 @@ const router = new Router(); router.get('/', async (request, response) => { const renderData = {}; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } diff --git a/src/routes/progress.js b/src/routes/progress.js index eacd849..6a70d3e 100644 --- a/src/routes/progress.js +++ b/src/routes/progress.js @@ -10,9 +10,7 @@ router.get('/', async (request, response) => { boards }; - renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - if (renderData.isLoggedIn) { + if (response.locals.isLoggedIn) { const account = await util.getAccount(request, response); renderData.account = account; } From d6713e8e739b8098fd1dac2cf54ffd7918186e35 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 10:05:33 -0400 Subject: [PATCH 16/22] Always assign user render data if logged in --- src/middleware/pnid.js | 21 ---------------- src/middleware/render-data.js | 19 +++++++++++++-- src/middleware/require-login.js | 10 ++++++++ src/routes/account.js | 14 +++++------ src/routes/aprilfools.js | 11 +-------- src/routes/blog.js | 11 --------- src/routes/docs.js | 11 --------- src/routes/home.js | 6 ----- src/routes/localization.js | 10 +------- src/routes/progress.js | 6 ----- src/util.js | 43 +-------------------------------- 11 files changed, 37 insertions(+), 125 deletions(-) delete mode 100644 src/middleware/pnid.js create mode 100644 src/middleware/require-login.js diff --git a/src/middleware/pnid.js b/src/middleware/pnid.js deleted file mode 100644 index 507da57..0000000 --- a/src/middleware/pnid.js +++ /dev/null @@ -1,21 +0,0 @@ -const util = require('../util'); -const database = require('../database'); - -async function pnidMiddleware(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}`); - } - - try { - request.account = await util.getUserAccountData(request, response); - request.pnid = await database.PNID.findOne({ pid: request.account.pid }); - - return next(); - } catch (error) { - response.cookie('error_message', error.message, { domain: '.pretendo.network' }); - return response.redirect('/account/login'); - } -} - -module.exports = pnidMiddleware; \ No newline at end of file diff --git a/src/middleware/render-data.js b/src/middleware/render-data.js index 8673377..c434012 100644 --- a/src/middleware/render-data.js +++ b/src/middleware/render-data.js @@ -1,4 +1,5 @@ const util = require('../util'); +const database = require('../database'); async function renderDataMiddleware(request, response, next) { // Get user local @@ -17,8 +18,22 @@ async function renderDataMiddleware(request, response, next) { response.clearCookie('error_message', { domain: '.pretendo.network' }); response.locals.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token; - - return next(); + + 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; \ No newline at end of file diff --git a/src/middleware/require-login.js b/src/middleware/require-login.js new file mode 100644 index 0000000..873e2e8 --- /dev/null +++ b/src/middleware/require-login.js @@ -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; \ No newline at end of file diff --git a/src/routes/account.js b/src/routes/account.js index 771b712..cbcbb73 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -6,7 +6,7 @@ const AdmZip = require('adm-zip'); const Stripe = require('stripe'); const { REST: DiscordRest } = require('@discordjs/rest'); const { Routes: DiscordRoutes } = require('discord-api-types/v10'); -const pnidMiddleware = require('../middleware/pnid'); +const requireLoginMiddleware = require('../middleware/require-login'); const database = require('../database'); const cache = require('../cache'); const util = require('../util'); @@ -27,7 +27,7 @@ const discordOAuth = new DiscordOauth2({ version: 'v10' }); -router.get('/', pnidMiddleware, async (request, response) => { +router.get('/', requireLoginMiddleware, async (request, response) => { // Setup the data to be sent to the handlebars renderer const renderData = {}; @@ -145,7 +145,7 @@ router.get('/logout', async(_request, response) => { response.redirect('/'); }); -router.get('/connect/discord', pnidMiddleware, async (request, response) => { +router.get('/connect/discord', requireLoginMiddleware, async (request, response) => { let tokens; try { // Attempt to get OAuth2 tokens @@ -173,7 +173,7 @@ router.get('/connect/discord', pnidMiddleware, async (request, response) => { } }); -router.post('/online-files', pnidMiddleware, async (request, response) => { +router.post('/online-files', requireLoginMiddleware, async (request, response) => { const { account } = request; const { password } = request.body; @@ -265,7 +265,7 @@ router.get('/miieditor', async (request, response) => { }); }); -router.get('/upgrade', pnidMiddleware, async (request, response) => { +router.get('/upgrade', requireLoginMiddleware, async (request, response) => { // Set user account info to render data const { pnid } = request; @@ -306,7 +306,7 @@ router.get('/upgrade', pnidMiddleware, async (request, response) => { response.render('account/upgrade', renderData); }); -router.post('/stripe/checkout/:priceId', pnidMiddleware, async (request, response) => { +router.post('/stripe/checkout/:priceId', requireLoginMiddleware, async (request, response) => { // Set user account info to render data const { account } = request; const pid = account.pid; @@ -367,7 +367,7 @@ router.post('/stripe/checkout/:priceId', pnidMiddleware, async (request, respons } }); -router.post('/stripe/unsubscribe', pnidMiddleware, async (request, response) => { +router.post('/stripe/unsubscribe', requireLoginMiddleware, async (request, response) => { // Set user account info to render data const { pnid } = request; diff --git a/src/routes/aprilfools.js b/src/routes/aprilfools.js index 74d7454..94bdb4b 100644 --- a/src/routes/aprilfools.js +++ b/src/routes/aprilfools.js @@ -1,17 +1,8 @@ const { Router } = require('express'); -const util = require('../util'); const router = new Router(); router.get('/', async (request, response) => { - - const renderData = {}; - - if (response.locals.isLoggedIn) { - const account = await util.getAccount(request, response); - renderData.account = account; - } - - response.render('aprilfools', renderData); + response.render('aprilfools'); }); module.exports = router; diff --git a/src/routes/blog.js b/src/routes/blog.js index 2b27e23..2e80238 100644 --- a/src/routes/blog.js +++ b/src/routes/blog.js @@ -1,5 +1,4 @@ const { Router } = require('express'); -const util = require('../util'); const logger = require('../logger'); const router = new Router(); @@ -40,11 +39,6 @@ router.get('/', async (request, response) => { postList }; - if (response.locals.isLoggedIn) { - const account = await util.getAccount(request, response); - renderData.account = account; - } - response.render('blog/blog', renderData); }); @@ -72,11 +66,6 @@ router.get('/:slug', async (request, response, next) => { postList, }; - if (response.locals.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; diff --git a/src/routes/docs.js b/src/routes/docs.js index 5f6697a..4541988 100644 --- a/src/routes/docs.js +++ b/src/routes/docs.js @@ -1,5 +1,4 @@ const { Router } = require('express'); -const util = require('../util'); const router = new Router(); const fs = require('fs'); @@ -15,11 +14,6 @@ router.get('/search', async (request, response) => { currentPage: request.params.slug }; - if (response.locals.isLoggedIn) { - const account = await util.getAccount(request, response); - renderData.account = account; - } - response.render('docs/search', renderData); }); @@ -28,11 +22,6 @@ router.get('/:slug', async (request, response, next) => { currentPage: request.params.slug }; - if (response.locals.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; diff --git a/src/routes/home.js b/src/routes/home.js index fc21522..9170877 100644 --- a/src/routes/home.js +++ b/src/routes/home.js @@ -1,5 +1,4 @@ const { Router } = require('express'); -const util = require('../util'); const { boards } = require('../../boards/boards.json'); const router = new Router(); @@ -11,11 +10,6 @@ router.get('/', async (request, response) => { boards }; - if (response.locals.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 diff --git a/src/routes/localization.js b/src/routes/localization.js index 7e4dec3..6706f4c 100644 --- a/src/routes/localization.js +++ b/src/routes/localization.js @@ -1,16 +1,8 @@ const { Router } = require('express'); -const util = require('../util'); const router = new Router(); router.get('/', async (request, response) => { - const renderData = {}; - - if (response.locals.isLoggedIn) { - const account = await util.getAccount(request, response); - renderData.account = account; - } - - response.render('localization', renderData); + response.render('localization'); }); module.exports = router; diff --git a/src/routes/progress.js b/src/routes/progress.js index 6a70d3e..fb133f4 100644 --- a/src/routes/progress.js +++ b/src/routes/progress.js @@ -1,5 +1,4 @@ const { Router } = require('express'); -const util = require('../util'); const { boards } = require('../../boards/boards.json'); const router = new Router(); @@ -10,11 +9,6 @@ router.get('/', async (request, response) => { boards }; - if (response.locals.isLoggedIn) { - const account = await util.getAccount(request, response); - renderData.account = account; - } - const trelloCache = await getTrelloCache(); renderData.progressLists = trelloCache; const stripeDonationCache = await getStripeDonationCache(); diff --git a/src/util.js b/src/util.js index d7a561a..b542f74 100644 --- a/src/util.js +++ b/src/util.js @@ -335,46 +335,6 @@ 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, @@ -387,6 +347,5 @@ module.exports = { getUserAccountData, updateDiscordConnection, nintendoPasswordHash, - handleStripeEvent, - getAccount + handleStripeEvent }; \ No newline at end of file From f537e5e97055b0fa0f0c9d8648b5627ee51094c2 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 10:07:38 -0400 Subject: [PATCH 17/22] Dont run render-data middleware whem requesting assets --- src/middleware/render-data.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/middleware/render-data.js b/src/middleware/render-data.js index c434012..38bcf14 100644 --- a/src/middleware/render-data.js +++ b/src/middleware/render-data.js @@ -2,6 +2,10 @@ 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); From 6deca851c56aa9e72a57de87b147c76ae631868e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 10:11:18 -0400 Subject: [PATCH 18/22] Update config example --- example.config.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/example.config.json b/example.config.json index 2949df2..7afb450 100644 --- a/example.config.json +++ b/example.config.json @@ -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", From d35d0315834eafc768b7ec118c67f2b8e4e40737 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 10:15:24 -0400 Subject: [PATCH 19/22] Render users actual Mii in Mii editor --- src/routes/account.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/routes/account.js b/src/routes/account.js index cbcbb73..be84c13 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -214,9 +214,8 @@ router.post('/online-files', requireLoginMiddleware, async (request, response) = response.end(onlineFiles.toBuffer()); }); -router.get('/miieditor', async (request, response) => { - // Should obviously be the user's - const encodedUserMiiData = 'AwAAQOlVognnx0GC2X0LLQOzuI0n2QAAAUBiAGUAbABsAGEAAABFAAAAAAAAAEBAEgCBAQRoQxggNEYUgRIXaA0AACkDUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP6G'; +router.get('/miieditor', requireLoginMiddleware, async (request, response) => { + const { account } = request; // Adapted from https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values @@ -260,7 +259,7 @@ router.get('/miieditor', async (request, response) => { }; response.render('account/miieditor', { - encodedUserMiiData, + encodedUserMiiData: account.mii.data, editorToHex }); }); From 41ad68990c778fef8e732a42eabebfccb34887ca Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 16 Jul 2022 10:34:26 -0400 Subject: [PATCH 20/22] Check for staff members again --- src/routes/account.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/account.js b/src/routes/account.js index be84c13..6ac84f9 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -338,8 +338,8 @@ router.post('/stripe/checkout/:priceId', requireLoginMiddleware, async (request, 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'); + response.cookie('error_message', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' }); + return response.redirect('/account'); } try { From da4ecacd3ca647313bfda747604eead9edf2a2c7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 18 Jul 2022 18:06:15 -0400 Subject: [PATCH 21/22] http -> https --- public/assets/js/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/assets/js/account.js b/public/assets/js/account.js index b1b0435..ee49c55 100644 --- a/public/assets/js/account.js +++ b/public/assets/js/account.js @@ -5,7 +5,7 @@ document.getElementById('remove-discord-connection')?.addEventListener('click', const tokenType = document.cookie.split('; ').find(row => row.startsWith('token_type=')).split('=')[1]; const accessToken = document.cookie.split('; ').find(row => row.startsWith('access_token=')).split('=')[1]; - fetch('http://api.pretendo.cc/v1/connections/remove/discord', { + fetch('https://api.pretendo.cc/v1/connections/remove/discord', { method: 'DELETE', headers: { 'Content-Type': 'application/json', From 0a05981995e98ee641980b7175d69e4d9f3d3d00 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 18 Jul 2022 18:06:37 -0400 Subject: [PATCH 22/22] removed stary .then() --- public/assets/js/account.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/assets/js/account.js b/public/assets/js/account.js index ee49c55..a73794f 100644 --- a/public/assets/js/account.js +++ b/public/assets/js/account.js @@ -44,7 +44,6 @@ onlineFilesModalButtonConfirm?.addEventListener('click', () => { }) }) .then(response => response.blob()) - .then() .then(blob => URL.createObjectURL(blob)) .then(blobUrl => { const a = document.createElement('a');