mirror of
https://github.com/PretendoNetwork/website.git
synced 2024-06-11 09:07:37 -04:00
Compare commits
5 commits
83ea4484bb
...
b184581281
Author | SHA1 | Date | |
---|---|---|---|
b184581281 | |||
79d22e2ec8 | |||
09b53b7814 | |||
255e68a881 | |||
e3e8c6f4aa |
|
@ -1,23 +1,24 @@
|
|||
# 3DS/2DS Family
|
||||
|
||||
<div class="tip red">
|
||||
<strong>CAUTION:</strong>
|
||||
SYSTEM TRANSFERS ARE NOT CURRENTLY SUPPORTED BY OUR SERVERS. ATTEMPTING TO PERFORM A SYSTEM TRANSFER MAY PREVENT YOU FROM BEING ABLE TO GO ONLINE IN THE FUTURE. SUPPORT FOR SYSTEM TRANSFERS IS IN DEVELOPMENT.
|
||||
</div>
|
||||
## Things to know before you start
|
||||
|
||||
<div class="tip red">
|
||||
<strong>CAUTION:</strong>
|
||||
Collecting badges in Nintendo Badge Arcade while connected to one network and then launching the game on a different network will result in your badges disappearing. This occurs because the locally saved data does not match the data stored on the server.
|
||||
</div>
|
||||
This guide assumes that you have a **Homebrewed System** running the latest version of Luma3DS (13+). If you are unsure or do not have a homebrewed system, please follow the [3DS Hacks Guide](https://3ds.hacks.guide/) to check, install and update it.
|
||||
|
||||
<div class="tip">
|
||||
ℹ️ This guide assumes that you have a <b>Homebrewed System running the latest version of Luma3DS (13+)</b>, if you don't please follow this <a href="https://3ds.hacks.guide/" target="_blank">guide</a> on how to homebrew your system first.
|
||||
</div>
|
||||
**Support for system transfer is currently in development**. Attempting to perform one while being connected to Pretendo will prevent you from being able to go online in the future. If you need to do a system transfer, please do it before continuing with this guide.
|
||||
|
||||
The following steps are required for you to connect to the Pretendo Network:
|
||||
Badges in **Nintendo Badge Arcade** might disappear and become de-synchronized when switching between Pretendo and Nintendo Network. This is due to your local data not matching the data stored on the respective server.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To connect to the Pretendo Network, you will need to install a homebrew application and enable Luma patches on your console.
|
||||
|
||||
Nimbus is our homebrew application that allows you to connect and switch from Nintendo Network to the Pretendo Network.
|
||||
|
||||
This guide is divided into the following steps:
|
||||
1. [Downloading Nimbus](#downloading-nimbus)
|
||||
2. [Enabling Luma patches](#luma-patches)
|
||||
3. [Nimbus](#using-nimbus)
|
||||
3. [Using Nimbus](#using-nimbus)
|
||||
4. [Signing into your PNID](#signing-into-your-pnid)
|
||||
|
||||
## Downloading Nimbus
|
||||
|
||||
|
|
|
@ -92,6 +92,11 @@ async function renderDataMiddleware(request, response, next) {
|
|||
request.pnid = await database.PNID.findOne({ pid: response.locals.account.pid });
|
||||
request.account = response.locals.account;
|
||||
|
||||
if (request.pnid.deleted) {
|
||||
// TODO - We just need to overhaul our API tbh
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
|
|
|
@ -71,7 +71,8 @@ router.get('/', requireLoginMiddleware, async (request, response) => {
|
|||
|
||||
router.get('/login', async (request, response) => {
|
||||
const renderData = {
|
||||
error: request.cookies.error_message
|
||||
error: request.cookies.error_message,
|
||||
loginPath: '/account/login'
|
||||
};
|
||||
|
||||
response.render('account/login', renderData);
|
||||
|
@ -88,7 +89,6 @@ router.post('/login', async (request, response) => {
|
|||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect(request.redirect || '/account');
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
|
@ -407,5 +407,138 @@ router.post('/stripe/webhook', async (request, response) => {
|
|||
response.json({ received: true });
|
||||
});
|
||||
|
||||
router.get('/sso/discourse', async (request, response, next) => {
|
||||
if (!request.query.sso || !request.query.sig) {
|
||||
return next(); // * 404
|
||||
}
|
||||
|
||||
const signature = util.signDiscoursePayload(request.query.sso);
|
||||
|
||||
if (signature !== request.query.sig) {
|
||||
return next(); // * 404
|
||||
}
|
||||
|
||||
const decodedPayload = new URLSearchParams(Buffer.from(request.query.sso, 'base64').toString());
|
||||
|
||||
if (!decodedPayload.has('nonce') || !decodedPayload.has('return_sso_url')) {
|
||||
return next(); // * 404
|
||||
}
|
||||
|
||||
// * User already logged in, don't show the login prompt
|
||||
if (request.cookies.access_token && request.cookies.refresh_token) {
|
||||
try {
|
||||
const accountData = await util.getUserAccountData(request, response);
|
||||
|
||||
// * Discourse REQUIRES unique emails, however we do not due to NN also
|
||||
// * not requiring unique email addresses. Email addresses, for now,
|
||||
// * are faked using the users PID. This will essentially disable email
|
||||
// * for the forum, but it's a bullet we have to bite for right now.
|
||||
// TODO - We can run our own SMTP server which maps fake emails (pid@pretendo.whatever) to users real emails
|
||||
const payload = Buffer.from(new URLSearchParams({
|
||||
nonce: decodedPayload.get('nonce'),
|
||||
external_id: accountData.pid,
|
||||
email: `${accountData.pid}@invalid.com`, // * Hack to get unique emails
|
||||
username: accountData.username,
|
||||
name: accountData.username,
|
||||
avatar_url: accountData.mii.image_url,
|
||||
avatar_force_update: true
|
||||
}).toString()).toString('base64');
|
||||
|
||||
const query = new URLSearchParams({
|
||||
sso: payload,
|
||||
sig: util.signDiscoursePayload(payload)
|
||||
}).toString();
|
||||
|
||||
return response.redirect(`${decodedPayload.get('return_sso_url')}?${query}`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/logout');
|
||||
}
|
||||
}
|
||||
|
||||
// * User not logged in already, show the login page
|
||||
const renderData = {
|
||||
discourse: {
|
||||
// * Fast and dirty sanitization. If the strings contain
|
||||
// * characters not allow in their encodings, they are removed
|
||||
// * when doing this decode-encode. Since neither base64/hex
|
||||
// * allow characters such as < and >, this prevents injection.
|
||||
payload: Buffer.from(request.query.sso, 'base64').toString('base64'),
|
||||
signature: Buffer.from(request.query.sig, 'hex').toString('hex')
|
||||
},
|
||||
loginPath: '/account/sso/discourse'
|
||||
};
|
||||
|
||||
response.render('account/login', renderData); // * Just reuse the /account/login page, no need to duplicate the pages
|
||||
});
|
||||
|
||||
router.post('/sso/discourse', async (request, response, next) => {
|
||||
if (!request.body['discourse-sso-payload'] || !request.body['discourse-sso-signature']) {
|
||||
return next(); // * 404
|
||||
}
|
||||
|
||||
const { username, password } = request.body;
|
||||
|
||||
// * Fast and dirty sanitization. If the strings contain
|
||||
// * characters not allow in their encodings, they are removed
|
||||
// * when doing this decode-encode. Since neither base64/hex
|
||||
// * allow characters such as < and >, this prevents injection.
|
||||
const discoursePayload = Buffer.from(request.body['discourse-sso-payload'], 'base64').toString('base64');
|
||||
const discourseSignature = Buffer.from(request.body['discourse-sso-signature'], 'hex').toString('hex');
|
||||
|
||||
const signature = util.signDiscoursePayload(discoursePayload);
|
||||
|
||||
if (signature !== discourseSignature) {
|
||||
return next(); // * 404
|
||||
}
|
||||
|
||||
const decodedPayload = new URLSearchParams(Buffer.from(discoursePayload, 'base64').toString());
|
||||
|
||||
if (!decodedPayload.has('nonce') || !decodedPayload.has('return_sso_url')) {
|
||||
return next(); // * 404
|
||||
}
|
||||
|
||||
try {
|
||||
const tokens = await util.login(username, password);
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
// * Need to set these here so that getUserAccountData can see them
|
||||
request.cookies.refresh_token = tokens.refresh_token;
|
||||
request.cookies.access_token = tokens.access_token;
|
||||
request.cookies.token_type = tokens.token_type;
|
||||
|
||||
const accountData = await util.getUserAccountData(request, response);
|
||||
|
||||
// * Discourse REQUIRES unique emails, however we do not due to NN also
|
||||
// * not requiring unique email addresses. Email addresses, for now,
|
||||
// * are faked using the users PID. This will essentially disable email
|
||||
// * for the forum, but it's a bullet we have to bite for right now.
|
||||
// TODO - We can run our own SMTP server which maps fake emails (pid@pretendo.whatever) to users real emails
|
||||
const payload = Buffer.from(new URLSearchParams({
|
||||
nonce: decodedPayload.get('nonce'),
|
||||
external_id: accountData.pid,
|
||||
email: `${accountData.pid}@invalid.com`, // * Hack to get unique emails
|
||||
username: accountData.username,
|
||||
name: accountData.username,
|
||||
avatar_url: accountData.mii.image_url,
|
||||
avatar_force_update: true
|
||||
}).toString()).toString('base64');
|
||||
|
||||
const query = new URLSearchParams({
|
||||
sso: payload,
|
||||
sig: util.signDiscoursePayload(payload)
|
||||
}).toString();
|
||||
|
||||
return response.redirect(`${decodedPayload.get('return_sso_url')}?${query}`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
@ -2,6 +2,7 @@ const { Schema } = require('mongoose');
|
|||
|
||||
// Only define what we will be using
|
||||
const PNIDSchema = new Schema({
|
||||
deleted: Boolean,
|
||||
pid: {
|
||||
type: Number,
|
||||
unique: true
|
||||
|
|
|
@ -247,6 +247,10 @@ async function removeDiscordMemberTesterRole(memberId) {
|
|||
}
|
||||
}
|
||||
|
||||
function signDiscoursePayload(payload) {
|
||||
return crypto.createHmac('sha256', config.discourse.sso.secret).update(payload).digest('hex');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fullUrl,
|
||||
getLocale,
|
||||
|
@ -265,5 +269,6 @@ module.exports = {
|
|||
assignDiscordMemberSupporterRole,
|
||||
assignDiscordMemberTesterRole,
|
||||
removeDiscordMemberSupporterRole,
|
||||
removeDiscordMemberTesterRole
|
||||
removeDiscordMemberTesterRole,
|
||||
signDiscoursePayload
|
||||
};
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="account-form-wrapper">
|
||||
<form action="/account/login" method="post" class="account">
|
||||
<form action="{{ loginPath }}" method="post" class="account">
|
||||
<h2>{{ locale.account.loginForm.login }}</h2>
|
||||
<p>{{ locale.account.loginForm.detailsPrompt }}</p>
|
||||
<div>
|
||||
|
@ -23,8 +22,10 @@
|
|||
<input name="redirect" id="redirect" type="hidden" value="{{redirect}}">
|
||||
<div class="buttons">
|
||||
<button type="submit">{{ locale.account.loginForm.login }}</button>
|
||||
<a href="/account/register{{#if redirect}}?redirect={{redirect}}{{/if}}" class="register">{{ locale.account.loginForm.registerPrompt }}</a>
|
||||
<a href="/account/register{{#if redirect}}?redirect={{redirect}}{{/if}}" class="register">{{ locale.account.loginForm.registerPrompt }}</a>
|
||||
</div>
|
||||
<input type="hidden" id="discourse-sso-payload" name="discourse-sso-payload" value="{{ discourse.payload }}" />
|
||||
<input type="hidden" id="discourse-sso-signature" name="discourse-sso-signature" value="{{ discourse.signature }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
<a href="/progress" class="hide-on-mobile">
|
||||
<button>{{ locale.nav.progress }}</button>
|
||||
</a>
|
||||
<a href="https://forum.pretendo.network" class="hide-on-mobile">
|
||||
<button>Forum</button> <!-- TODO - Translate this -->
|
||||
</a>
|
||||
<a href="/account/upgrade" class="donate">
|
||||
<button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
|
@ -161,6 +164,15 @@
|
|||
<p class="caption">{{ locale.nav.dropdown.captions.blog }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://forum.pretendo.network">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48H56a16 16 0 0 0-16 16v120a8 8 0 0 1-16 0V88a8 8 0 0 0-16 0v96a24 24 0 0 0 24 24h176a24.1 24.1 0 0 0 24-24V64a16 16 0 0 0-16-16Zm-40 104H96a8 8 0 0 1 0-16h80a8 8 0 0 1 0 16Zm0-32H96a8 8 0 0 1 0-16h80a8 8 0 0 1 0 16Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">Forum</p> <!-- TODO - Translate this -->
|
||||
<p class="caption">Chat with others and get support</p> <!-- TODO - Translate this -->
|
||||
</div>
|
||||
</a>
|
||||
<a href="/progress">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M96 8a8 8 0 0 1 8-8h48a8 8 0 0 1 0 16h-48a8 8 0 0 1-8-8Zm128 120a96 96 0 1 1-96-96a96.2 96.2 0 0 1 96 96Zm-50.7-45.3a8.1 8.1 0 0 0-11.4 0l-39.6 39.6a8.1 8.1 0 0 0 0 11.4a8.2 8.2 0 0 0 11.4 0l39.6-39.6a8.1 8.1 0 0 0 0-11.4Z"/></svg>
|
||||
|
|
Loading…
Reference in a new issue