Merge pull request #84 from PretendoNetwork/dev

Merge dev into master
This commit is contained in:
Jonathan Barrow 2021-11-28 09:26:03 -05:00 committed by GitHub
commit 0e78b7f922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 3059 additions and 386 deletions

View File

@ -1,5 +1,6 @@
{
"env": {
"browser": true,
"node": true,
"commonjs": true,
"es6": true

View File

@ -1,6 +1,6 @@
---
title: "Test"
author: "Monty"
author: "monty"
author_image: "https://www.github.com/ashmonty.png"
date: "January 20, 2038"
caption: "A post to test the styling of the various elements we might use (rename to _test.md before deploying the blog section)"

View File

@ -10,6 +10,11 @@
},
"discord": {
"client_id": "client_id",
"client_secret": "client_secret"
"client_secret": "client_secret",
"guild_id": "Guild ID",
"bot_token": "token",
"tester_roles": [
"role id"
]
}
}

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "مطور الويب",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Desenvolvimento web",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Webentwicklung",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Desarrollo web",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -109,7 +109,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Développement Web",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Sviluppo web",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "웹 개발",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Dezvoltare web",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Web-разработка",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Web sayfası geliştirme",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

View File

@ -107,7 +107,7 @@
"github": "https://github.com/jipfr"
},
{
"name": "Monty",
"name": "monty",
"caption": "Web development",
"picture": "https://github.com/ashmonty.png",
"github": "https://github.com/ashmonty"

2602
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,18 +19,16 @@
"dependencies": {
"colors": "^1.4.0",
"cookie-parser": "^1.4.5",
"discord-oauth2": "github:ryanblenis/discord-oauth2",
"express": "^4.17.1",
"express-handlebars": "^4.0.4",
"express-locale": "^2.0.0",
"fs-extra": "^9.1.0",
"got": "^11.8.2",
"gray-matter": "^4.0.3",
"ioredis": "^4.26.0",
"marked": "^3.0.4",
"morgan": "^1.10.0",
"node-fetch": "^3.0.0",
"redis-json": "^5.0.0",
"trello": "^0.10.0"
"trello": "^0.11.0"
},
"devDependencies": {
"eslint": "^7.32.0"

View File

@ -5,14 +5,14 @@
color: var(--text-secondary);
}
/* Account settings navbar */
/* Account settings sidebar */
.account-sidebar .user {
text-align: center;
margin: 55px auto;
width: fit-content;
}
.account-sidebar .user .miiname {
font-size: 1.3rem;
font-size: 1.2rem;
color: var(--text);
margin: 8px 0 4px;
}
@ -20,8 +20,8 @@
margin: 0px;
}
.account-sidebar .user .mii {
width: 300px;
height: 300px;
width: 128px;
height: 128px;
border-radius: 100%;
background: var(--btn-secondary);
}
@ -30,7 +30,7 @@
.settings-wrapper {
display: grid;
grid-column-start: 2;
grid-template-columns: 50%;
grid-template-columns: 1fr 1fr;
column-gap: 20px;
}
.settings-wrapper a {
@ -126,6 +126,13 @@ fieldset {
color: var(--text);
}
.setting-card #remove-discord-connection {
width: 100%;
padding: 12px 48px;
cursor: pointer;
background: #383f6b;
}
.setting-card.sign-in-history a button {
width: 100%;
padding: 12px 48px;
@ -150,34 +157,37 @@ fieldset {
grid-column: 1 / span 2;
}
.account-link-notice {
display: flex;
justify-content: center;
position: absolute;
top: -150px;
width: 100%;
animation: account-link-notice 5s;
}
@keyframes account-link-notice {
@keyframes banner-notice {
0% {top: -150px}
20% {top: 35px}
80% {top: 35px}
100% {top: -150px}
}
.account-link-notice div {
background: #37A985;
.banner-notice {
display: flex;
justify-content: center;
position: fixed;
top: -150px;
width: 100%;
animation: banner-notice 5s;
}
.banner-notice div {
padding: 4px 36px;
border-radius: 5px;
z-index: 3;
}
.banner-notice.success div {
background: #37A985;
}
.banner-notice.error div {
background: #A9375B;
}
footer {
margin-top: 80px;
}
@media screen and (max-width: 1500px) {
@media screen and (max-width: 1300px) {
.account-wrapper {
margin: 20px 0;
}
@ -191,12 +201,12 @@ footer {
}
.account-sidebar .user .mii {
width: 250px;
height: 250px;
width: 128px;
height: 128px;
}
}
@media screen and (max-width: 1050px) {
@media screen and (max-width: 1000px) {
.settings-wrapper {
display: block;
width: 100%;
@ -209,18 +219,28 @@ footer {
@media screen and (max-width: 550px) {
.setting-card {
padding: 24px;
padding: 24px;
width: calc(100vw - 48px);
margin-left: -5vw;
margin-right: -2.5vw;
border-radius: 0;
margin-bottom: 12px;
}
.setting-card .edit {
top: 24px;
right: 24px;
}
.setting-card .setting-list {
grid-template-columns: auto;
top: 20px;
right: 20px;
transform: scale(0.85);
}
.setting-card .server-selection {
flex-flow: column;
}
}
@media screen and (max-width: 350px) {
.setting-card .setting-list {
grid-template-columns: auto;
}
}

108
public/assets/css/login.css Normal file
View File

@ -0,0 +1,108 @@
.wrapper {
display: flex;
flex-flow: column;
min-height: 100vh;
}
.login-wrapper {
margin: auto;
}
form.login {
padding: 48px;
background-color: #151934;
border-radius: 12px;
max-width: calc(90vw - 92px);
width: 400px;
}
form.login input {
appearance: none;
-webkit-appearance: none;
display: block;
border: 0;
font-family: Poppins, Arial, Helvetica, sans-serif;
font-size: 1rem;
background-color: var(--btn-secondary);
border: none;
border-radius: 4px;
padding: 12px;
color: var(--text);
width: calc(100% - 24px);
margin-bottom: 12px;
}
form.login input::placeholder {
color: var(--text-secondary);
}
form.login input:focus {
background-color: #fff;
color: var(--btn-secondary);
transition: 200ms;
outline: none;
}
form.login button {
appearance: none;
-webkit-appearance: none;
display: block;
border: 0;
border-radius: 4px;
font-family: Poppins, Arial, Helvetica, sans-serif;
font-size: 1rem;
color: var(--text);
padding: 12px;
background: var(--btn);
cursor: pointer;
width: 100%;
}
form.login a.pwdreset {
display: block;
color: var(--text-secondary);
text-decoration: none;
text-align: right;
margin: 6px 0;
width: 100%;
}
form.login a.pwdreset:hover {
color: var(--text);
}
form.login .register {
text-decoration: none;
}
form.login .register button {
background: var(--btn-secondary);
margin-top: 18px;
}
footer {
margin-top: 23px;
}
@keyframes banner-notice {
0% {top: -150px}
20% {top: 35px}
80% {top: 35px}
100% {top: -150px}
}
.banner-notice {
display: flex;
justify-content: center;
position: fixed;
top: -150px;
width: 100%;
animation: banner-notice 5s;
}
.banner-notice div {
padding: 4px 36px;
border-radius: 5px;
z-index: 3;
}
.banner-notice.error div {
background: #A9375B;
}

View File

@ -0,0 +1,22 @@
document.getElementById('remove-discord-connection').addEventListener('click', () => {
// TODO: Refresh access token if expired (move this to the backend maybe?)
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', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `${tokenType} ${decodeURIComponent(accessToken)}`
}
})
.then(response => response.json())
.then(({ status }) => {
if (status === 200) {
location.reload();
}
})
.catch(console.log);
});

View File

@ -1,185 +1,284 @@
const { Router } = require('express');
const crypto = require('crypto');
const DiscordOauth2 = require('discord-oauth2');
const util = require('../util');
const router = new Router();
const fetch = (...args) =>
import('node-fetch').then(({ default: fetch }) => fetch(...args));
const config = require('../../config.json');
const router = new Router();
// A test PNID
const account = {
access_level: 0, // 0: standard, 1: tester, 2: mod?, 3. dev?, 4. god
server_access_level: 'prod', // prod, test, dev
username: 'PN_Monty',
password: 'thelegend27',
birthdate: '13/04/2004', // what format
gender: 'what even is this', // what format
country: 'Italy', // what format
language: 'JS smh', // what format
email: {
address: 'notascam@freecreditreport.com',
primary: true, // do we need to let the user change anything?
parent: true,
reachable: true,
validated: true,
},
region: 'don\'t know the numbers',
timezone: {
name: 'Europe/Rome',
offset: 3600, // haven't checked if this is correct, just an assumption
},
mii: {
name: 'Monty',
image_url:
'https://studio.mii.nintendo.com/miis/image.png?data=00080f50595a606a6268696f757883969b9aa1a8b1b8b7bebdc5cccbd1d8620a121a181119111916222d3444484c4b&type=face&expression=normal&width=512',
},
flags: {
marketing: true,
off_device: true, // Forgot what this does
},
devices: [
{
is_emulator: {
type: false,
},
console_type: {
type: 'wup',
},
device_attributes: {
created_date: '23/07/2021', // what format
name: 'Wii U', // ?
value: 'what', // what format
},
},
{
is_emulator: {
type: false,
},
console_type: {
type: 'wup',
},
device_attributes: {
created_date: '24/05/2020',
name: 'Windows', // ?
value: 'what',
},
},
],
connections: { // This needs to be added to the schema
discord: {
id: '406125028065804289',
access_token: 'GiJ2Osi6LpYS7uLgZNyvCbRtpRopv1', // This only has the identify scope so eh, who cares if it gets leaked
expires_on: 604800,
refresh_token: 'VeQMa6zp2Rx77PjhiNFJbpKrpz2gX2',
scope: 'identify',
token_type: 'Bearer',
},
},
};
// Create OAuth client
const discordOAuth = new DiscordOauth2({
clientId: config.discord.client_id,
clientSecret: config.discord.client_secret,
redirectUri: `${config.http.base_url}/account/connect/discord`,
version: 'v9'
});
router.get('/', async (request, response) => {
const justLinked = request.cookies.justLinked;
const reqLocale = request.locale;
const locale = util.getLocale(reqLocale.region, reqLocale.language);
let discordUser;
if (account.connections.discord.access_token) {
// TODO: check if the token is valid, if not refresh it.
const discord = account.connections.discord;
const fetchDiscordUser = await fetch('https://discord.com/api/users/@me', {
method: 'get',
headers: {
'Authorization': `${discord.token_type} ${discord.access_token}`
},
});
discordUser = await fetchDiscordUser.json();
// Verify the user is logged in
if (!request.cookies.access_token || !request.cookies.refresh_token) {
return response.redirect('/account/login');
}
// test user
discordUser = {
id: '406125028065804289',
username: 'montylion',
avatar: '5184b3dc388bdad1a93eb58694a58398',
discriminator: '3581',
public_flags: 64,
flags: 64,
banner: null,
banner_color: '#ff7081',
accent_color: 16740481,
locale: 'en-US',
mfa_enabled: true,
};
const isTester = () => {
return account.access_level > 0;
};
response.clearCookie('justLinked');
// TODO: make the sign in history only show the first 2/4 devices
response.render('account', {
// Setup the data to be sent to the handlebars renderer
const renderData = {
layout: 'main',
locale,
localeString: reqLocale.toString(),
account,
isTester,
discordUser,
justLinked
locale: util.getLocale(request.locale.region, request.locale.language),
localeString: request.locale.toString(),
linked: request.cookies.linked,
error: request.cookies.error
};
// Reset message cookies
response.clearCookie('linked');
response.clearCookie('error');
// 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;
renderData.account = account;
renderData.isTester = account.access_level > 0;
// Check if a Discord account is linked to the PNID
if (account.connections.discord.id && account.connections.discord.id.trim() !== '') {
// If Discord account is linked, then get user info
try {
renderData.discordUser = await discordOAuth.getUser(account.connections.discord.access_token);
} catch (error) {
// Assume expired, refresh and retry Discord request
let tokens;
try {
tokens = await discordOAuth.tokenRequest({
scope: 'identify guilds',
grantType: 'refresh_token',
refreshToken: account.connections.discord.refresh_token,
});
} catch (error) {
renderData.error = 'Invalid Discord refresh token. Remove account and relink';
response.render('account', renderData);
}
// TODO: Add a dedicated endpoint for updating connections?
apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
}, {
data: {
id: account.connections.discord.id,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
}
});
if (apiResponse.statusCode !== 200) {
// Assume expired, refresh and retry request
apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
refresh_token: request.cookies.refresh_token,
grant_type: 'refresh_token'
});
if (apiResponse.statusCode !== 200) {
// TODO: Error message
return response.status(apiResponse.statusCode).json({
error: 'Bad'
});
}
const tokens = apiResponse.body;
response.cookie('refresh_token', tokens.refresh_token, { domain : '.pretendo.network' });
response.cookie('access_token', tokens.access_token, { domain : '.pretendo.network' });
response.cookie('token_type', tokens.token_type, { domain : '.pretendo.network' });
apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
'Authorization': `${tokens.token_type} ${tokens.access_token}`
}, {
data: {
id: account.connections.discord.id,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
}
});
// If still failed, something went horribly wrong
if (apiResponse.statusCode !== 200) {
// TODO: Error message
return response.status(apiResponse.statusCode).json({
error: 'Bad'
});
}
}
account.connections.discord.access_token = tokens.access_token;
account.connections.discord.refresh_token = tokens.refresh_token;
}
// Get the users Discord roles to check if they are a tester
const { roles } = await discordOAuth.getMemberRolesForGuild({
userId: account.connections.discord.id,
guildId: config.discord.guild_id,
botToken: config.discord.bot_token
});
// Only run this check if not already a tester (edge case)
if (!renderData.isTester) {
// 409116477212459008 = Developer
// 882247322933801030 = Super Mario (Patreon tier)
renderData.isTester = roles.some(role => config.discord.tester_roles.includes(role));
}
} else {
// If no Discord account linked, generate an auth URL
const discordAuthURL = discordOAuth.generateAuthUrl({
scope: ['identify', 'guilds'],
state: crypto.randomBytes(16).toString('hex'),
});
renderData.discordAuthURL = discordAuthURL;
}
response.render('account', renderData);
});
router.get('/login', async (request, response) => {
const renderData = {
layout: 'main',
locale: util.getLocale(request.locale.region, request.locale.language),
localeString: request.locale.toString(),
error: request.cookies.error
};
response.clearCookie('error');
response.render('account_login', renderData);
});
router.post('/login', async (request, response) => {
const { username, password } = request.body;
const apiResponse = await util.apiPostGetRequest('/v1/login', {}, {
username,
password,
grant_type: 'password'
});
if (apiResponse.statusCode !== 200) {
response.cookie('error', apiResponse.body.error, { domain: '.pretendo.network' });
return response.redirect('/account/login');
}
const tokens = apiResponse.body;
response.cookie('refresh_token', tokens.refresh_token, { domain : '.pretendo.network' });
response.cookie('access_token', tokens.access_token, { domain : '.pretendo.network' });
response.cookie('token_type', tokens.token_type, { domain : '.pretendo.network' });
response.redirect('/account');
});
router.get('/connect/discord', async (request, response) => {
//const reqLocale = request.locale;
//const locale = util.getLocale(reqLocale.region, reqLocale.language);
const codeExchange = await fetch('https://discord.com/api/oauth2/token', {
method: 'post',
body: new URLSearchParams({
client_id: config.discord.client_id,
client_secret: config.discord.client_secret,
grant_type: 'authorization_code',
let tokens;
try {
// Attempt to get OAuth2 tokens
tokens = await discordOAuth.tokenRequest({
code: request.query.code,
redirect_uri: `${config.http.base_url}/account/connect/discord`,
}),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
const token = await codeExchange.json();
scope: 'identify guilds',
grantType: 'authorization_code',
});
} catch (error) {
response.cookie('error', 'Invalid Discord authorization code. Please try again', { domain: '.pretendo.network' });
return response.redirect('/account');
}
/*
// Send the oauth tokens to the account server
fetch('account server endpoint', {
method: 'post',
body: {
access_token: token.access_token,
token_type: token.token_type,
expires_on: new Date().getTime() + token.expires_in,
refresh_token: token.refresh_token,
scope: token.scope,
},
headers: {
'Content-Type': 'application/json',
// Get Discord user data
const user = await discordOAuth.getUser(tokens.access_token);
// Link the Discord account to the PNID
let apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
}, {
data: {
id: user.id,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
}
});
*/
/* Token refresh function (should be moved to util.js)
const refreshTokens = await fetch('https://discord.com/api/oauth2/token', {
method: 'post',
body: new URLSearchParams({
client_id: config.discord.client_id,
client_secret: config.discord.client_secret,
grant_type: 'refresh_token',
refresh_token: account.connections.discord.refresh_token
}),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
const newTokens = await refreshTokens.json();
console.log(newTokens);
*/
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'
});
// This sets a cookie to tell the account page to show the "Account linked successfully" notice, and redirects.
response.cookie('justLinked', 'discord').redirect('/account');
if (apiResponse.statusCode !== 200) {
// TODO: Error message
return response.status(apiResponse.statusCode).json({
error: 'Bad'
});
}
const tokens = apiResponse.body;
response.cookie('refresh_token', tokens.refresh_token, { domain : '.pretendo.network' });
response.cookie('access_token', tokens.access_token, { domain : '.pretendo.network' });
response.cookie('token_type', tokens.token_type, { domain : '.pretendo.network' });
apiResponse = await util.apiPostGetRequest('/v1/connections/add/discord', {
'Authorization': `${tokens.token_type} ${tokens.access_token}`
}, {
data: {
id: user.id,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
}
});
// If still failed, something went horribly wrong
if (apiResponse.statusCode !== 200) {
// TODO: Error message
return response.status(apiResponse.statusCode).json({
error: 'Bad'
});
}
}
response.cookie('linked', 'Discord', { domain: '.pretendo.network' }).redirect('/account');
});
module.exports = router;
module.exports = router;

View File

@ -14,6 +14,7 @@ const app = express();
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'));
@ -65,9 +66,6 @@ app.use(expressLocale({
'default': 'en-US'
}));
app.use('/', routers.home);
app.use('/faq', routers.faq);
app.use('/progress', routers.progress);
@ -108,6 +106,12 @@ app.engine('handlebars', handlebars({
${htmlRight}
</div>
`;
},
eq(value1, value2) {
return value1 === value2;
},
neq(value1, value2) {
return value1 !== value2;
}
}
}));

View File

@ -6,15 +6,20 @@ const trello = new Trello(config.trello.api_key, config.trello.api_token);
let cache;
async function getTrelloCache() {
const available = await trelloAPIAvailable();
if (!available) {
return {
update_time: Date.now(),
sections: []
};
}
if (!cache) {
cache = await updateTrelloCache();
}
if (cache.update_time < Date.now() - (1000 * 60 * 60)) {
const available = await trelloAPIAvailable();
if (available) {
cache = await updateTrelloCache();
}
cache = await updateTrelloCache();
}
return cache;

View File

@ -1,4 +1,5 @@
const fs = require('fs-extra');
const got = require('got');
const logger = require('./logger');
function fullUrl(request) {
@ -17,7 +18,44 @@ function getLocale(region, language) {
return require(`${__dirname}/../locales/US_en.json`);
}
function apiGetRequest(path, headers) {
return got.get(`https://api.pretendo.cc${path}`, {
responseType: 'json',
throwHttpErrors: false,
https: {
rejectUnauthorized: false, // Needed for self-signed certificates on localhost testing
},
headers
});
}
function apiPostGetRequest(path, headers, json) {
return got.post(`https://api.pretendo.cc${path}`, {
responseType: 'json',
throwHttpErrors: false,
https: {
rejectUnauthorized: false, // Needed for self-signed certificates on localhost testing
},
headers,
json
});
}
function apiDeleteGetRequest(path, headers, json) {
return got.delete(`https://api.pretendo.cc${path}`, {
throwHttpErrors: false,
https: {
rejectUnauthorized: false, // Needed for self-signed certificates on localhost testing
},
headers,
json
});
}
module.exports = {
fullUrl,
getLocale
getLocale,
apiGetRequest,
apiPostGetRequest,
apiDeleteGetRequest
};

View File

@ -6,11 +6,11 @@
<div class="account-wrapper">
<div class="account-sidebar">
<div class="user">
<img src="{{account.mii.image_url}}&bgColor=3E446600" class="mii" />
<p class="miiname">{{account.mii.name}}</p>
<p class="username">PNID: {{account.username}}</p>
</div>
<div class="user">
<img src="{{account.mii.image_url}}" class="mii" />
<p class="miiname">{{account.mii.name}}</p>
<p class="username">PNID: {{account.username}}</p>
</div>
</div>
<div class="settings-wrapper">
@ -50,8 +50,8 @@
<h2 class="header">Servers</h2>
<fieldset {{#if isTester}}{{else}}disabled{{/if}}>
<form class="server-selection" id="server">
<input type="radio" id="prod" name="server_selection" value="prod">
<label for="prod">
<input type="radio" id="prod" name="server_selection" value="prod" checked="{{ eq account.server_access_level 'prod'}}">
<label for="prod">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
<polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline>
@ -62,8 +62,8 @@
</svg>
<h2>Production</h2>
</label>
<input type="radio" id="beta" name="server_selection" value="beta">
<label for="beta">
<input type="radio" id="beta" name="server_selection" value="beta" checked="{{ neq account.server_access_level 'prod'}}">
<label for="beta">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="2,21 22,21 14,11.5 14,5 10,3 10,11.5"></polygon>
</svg>
@ -73,15 +73,14 @@
</fieldset>
{{#if discordUser}}
{{#if isTester }}
<p>Connected as {{ discordUser.username }}#{{ discordUser.discriminator }}.</p>
<p>Connected as {{ discordUser.username }}#{{ discordUser.discriminator }}</p>
<button class="button secondary" id="remove-discord-connection">Remove Discord account</button>
{{else}}
<p>{{ discordUser.username }}#{{ discordUser.discriminator }} doesn't appear to be a @Tester account. Check that you have the role on Discord. [should probably fix the wording]</p>
<p>Beta servers are exclusive to testers. To become a tester, check us out on <a href="https://www.patreon.com/PretendoNetwork" target="_blank">Patreon</a> and link your Discord account to your Patreon and PNID accounts</p>
{{/if}}
{{else}}
<p>Beta servers are exclusive to testers. If you're a tester, connect your Discord account <a href="https://discord.com/api/oauth2/authorize?client_id=896508671742345266&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Faccount%2Fconnect%2Fdiscord&response_type=code&scope=identify">here</a>.</p>
<p>Beta servers are exclusive to testers. If you're already a tester, connect your Discord account <a href="{{ discordAuthURL }}">here</a>.</p>
{{/if}}
</div>
<h2 class="section-header" id="security">Sign in and security</h2>
@ -109,11 +108,11 @@
<h2 class="header">Sign in history</h2>
<ul class="setting-list">
{{#each account.devices }}
<li>
<li>
<p class="label">{{this.device_attributes.name}}</p>
<p class="value">{{this.device_attributes.created_date}}</p>
</li>
{{/each}}
{{/each}}
</ul>
<a href="/account/sign-in-history">
<button class="button secondary">View full sign in history</button>
@ -124,29 +123,30 @@
<div class="setting-card span-both-columns">
<h2 class="header">Newsletter</h2>
<form id="other">
<input type="checkbox" id="marketing" name="marketing" {{#if account.flags.marketing}}checked{{/if}}>
<label for="marketing">Receive project updates via email (you can opt-out at any time)</label>
<input type="checkbox" id="marketing" name="marketing" {{#if account.flags.marketing}}checked{{/if}}>
<label for="marketing">Receive project updates via email (you can opt-out at any time)</label>
</form>
</div>
</div>
</div>
{{> footer }}
</div>
{{#if justLinked}}
<div class="account-link-notice">
<div>
<p>Account linked successfully.</p>
</div>
{{#if linked}}
<div class="banner-notice success">
<div>
<p>{{ linked }} account linked successfully</p>
</div>
</div>
{{/if}}
<script>
// Selectes the server level from the user's PNID
{{ account.server_access_level }}.setAttribute('checked', true);
</script>
{{#if error}}
<div class="banner-notice error">
<div>
<p>{{ error }}</p>
</div>
</div>
{{/if}}
<script src="/assets/js/account.js"></script>

View File

@ -0,0 +1,42 @@
<link rel="stylesheet" href="/assets/css/login.css" />
<div class="wrapper">
{{> header}}
<div class="login-wrapper">
<h1 class="title dot">Login</h1>
<form action="/account/login" method="post" class="login">
<div>
<input name="username" id="username" placeholder="Username" required>
</div>
<div>
<input name="password" id="password" type="password" placeholder="Password" required>
</div>
<input name="grant_type" id="grant_type" type="hidden" value="password">
<div>
<button type="submit">Login</button>
</div>
<div>
<a href="/account/passwordreset" class="pwdreset">Forgot your password?</a>
</div>
<div>
<a href="/account/register" class="register">
<button type="button">Don't have an account?</button>
</a>
</div>
</form>
</div>
{{> footer }}
</div>
{{#if error}}
<div class="banner-notice error">
<div>
<p>{{ error }}</p>
</div>
</div>
{{/if}}

View File

@ -43,5 +43,4 @@
</div>
{{> footer }}
</div>

View File

@ -1 +1 @@
<footer>Copyright 2021 - Design by mrjvs, development by Jip Fr & Monty</footer>
<footer>Copyright 2021 - Design by mrjvs, development by Jip Fr & monty</footer>