mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-03-21 17:24:28 -05:00
Discord account linking
This commit is contained in:
parent
ad11cd4ee1
commit
4079e3f99a
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"commonjs": true,
|
||||
"es6": true
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
2808
package-lock.json
generated
2808
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -19,15 +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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -153,7 +160,7 @@ fieldset {
|
|||
.account-link-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: -150px;
|
||||
width: 100%;
|
||||
animation: account-link-notice 5s;
|
||||
|
|
@ -173,6 +180,29 @@ fieldset {
|
|||
z-index: 3;
|
||||
}
|
||||
|
||||
.account-error-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: -150px;
|
||||
width: 100%;
|
||||
animation: account-error-notice 5s;
|
||||
}
|
||||
|
||||
@keyframes account-error-notice {
|
||||
0% {top: -150px}
|
||||
20% {top: 35px}
|
||||
80% {top: 35px}
|
||||
100% {top: -150px}
|
||||
}
|
||||
|
||||
.account-error-notice div {
|
||||
background: #A9375B;
|
||||
padding: 4px 36px;
|
||||
border-radius: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
|
|
|||
22
public/assets/js/account.js
Normal file
22
public/assets/js/account.js
Normal 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);
|
||||
});
|
||||
|
|
@ -1,185 +1,277 @@
|
|||
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);
|
||||
response.cookie('access_token', tokens.access_token);
|
||||
response.cookie('token_type', tokens.token_type);
|
||||
|
||||
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);
|
||||
response.cookie('access_token', tokens.access_token);
|
||||
response.cookie('token_type', tokens.token_type);
|
||||
|
||||
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) => {
|
||||
response.render('account_login');
|
||||
});
|
||||
|
||||
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) {
|
||||
// TODO: Error message
|
||||
return response.status(apiResponse.statusCode).json({
|
||||
error: 'Bad'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token);
|
||||
response.cookie('access_token', tokens.access_token);
|
||||
response.cookie('token_type', tokens.token_type);
|
||||
|
||||
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');
|
||||
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);
|
||||
response.cookie('access_token', tokens.access_token);
|
||||
response.cookie('token_type', tokens.token_type);
|
||||
|
||||
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').redirect('/account');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
|
|
|||
40
src/util.js
40
src/util.js
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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="account-link-notice">
|
||||
<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="account-error-notice">
|
||||
<div>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<script src="/assets/js/account.js"></script>
|
||||
14
views/account_login.handlebars
Normal file
14
views/account_login.handlebars
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<form action="/account/login" method="post">
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input name="username" id="username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password</label>
|
||||
<input name="password" id="password" type="password">
|
||||
</div>
|
||||
<input name="grant_type" id="grant_type" type="hidden" value="password">
|
||||
<div>
|
||||
<button>Login</button>
|
||||
</div>
|
||||
</form>
|
||||
Loading…
Reference in New Issue
Block a user