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