mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-04-24 15:37:12 -05:00
Merge pull request #177 from PretendoNetwork/feature-forgot-password
[Feature] Password resetting
This commit is contained in:
commit
53c6711c02
|
|
@ -261,6 +261,19 @@
|
|||
"registerPrompt": "Don't have an account?",
|
||||
"loginPrompt": "Already have an account?"
|
||||
},
|
||||
"forgotPassword": {
|
||||
"header": "Forgot Password",
|
||||
"sub": "Enter your email address/PNID below",
|
||||
"input": "Email address or PNID",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"resetPassword": {
|
||||
"header": "Reset Password",
|
||||
"sub": "Enter new password below",
|
||||
"password": "Password",
|
||||
"confirmPassword": "Confirm password",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"settings": {
|
||||
"downloadFiles": "Download account files",
|
||||
"downloadFilesDescription": "(will not work on Nintendo Network)",
|
||||
|
|
|
|||
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -3186,9 +3186,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
|
||||
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
|
|
@ -7022,9 +7022,9 @@
|
|||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
|
||||
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@
|
|||
"description": "",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"start": "browserify ./public/assets/js/miieditor.js -o ./public/assets/js/miieditor.bundled.js && node src/server.js"
|
||||
"start": "npm run build && node src/server.js",
|
||||
"build": "npm run build-miieditor && npm run build-forgot-password && npm run build-reset-password",
|
||||
"build-miieditor": "browserify ./public/assets/js/miieditor.js -o ./public/assets/js/miieditor.bundled.js",
|
||||
"build-forgot-password": "browserify ./public/assets/js/forgot-password.js -o ./public/assets/js/forgot-password.bundled.js",
|
||||
"build-reset-password": "browserify ./public/assets/js/reset-password.js -o ./public/assets/js/reset-password.bundled.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
132
public/assets/css/forgot-password.css
Normal file
132
public/assets/css/forgot-password.css
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
margin: 35px 0;
|
||||
}
|
||||
|
||||
.account-form-wrapper {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
form.account {
|
||||
display: block;
|
||||
padding: 40px 48px;
|
||||
background-color: var(--bg-shade-2);
|
||||
color: var(--text-shade-1);
|
||||
border-radius: 12px;
|
||||
width: min(480px, 90vw);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form.account h2 {
|
||||
margin: 0;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
form.account p {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
form.account div {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
form.account label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
form.account button {
|
||||
width: 100%;
|
||||
background: var(--accent-shade-0);
|
||||
}
|
||||
|
||||
form.account a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
color: var(--text-shade-1);
|
||||
text-align: right;
|
||||
margin: 6px 0;
|
||||
width: fit-content;
|
||||
}
|
||||
form.account a:hover {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
form.account a.pwdreset {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
form.account a.register {
|
||||
margin: auto;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
@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.success div {
|
||||
background: var(--green-shade-0);
|
||||
}
|
||||
|
||||
form.account.register {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
width: min(780px, 90vw);
|
||||
column-gap: 24px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
form.account.register div.h-captcha {
|
||||
grid-column: 1 / span 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
form.account.register p,
|
||||
form.account.register div.email,
|
||||
form.account.register div.buttons {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
form.account.register {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
form.account.register div.h-captcha,
|
||||
form.account.register p,
|
||||
form.account.register div.email,
|
||||
form.account.register div.buttons {
|
||||
grid-column: unset;
|
||||
}
|
||||
}
|
||||
132
public/assets/css/reset-password.css
Normal file
132
public/assets/css/reset-password.css
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
margin: 35px 0;
|
||||
}
|
||||
|
||||
.account-form-wrapper {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
form.account {
|
||||
display: block;
|
||||
padding: 40px 48px;
|
||||
background-color: var(--bg-shade-2);
|
||||
color: var(--text-shade-1);
|
||||
border-radius: 12px;
|
||||
width: min(480px, 90vw);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form.account h2 {
|
||||
margin: 0;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
form.account p {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
form.account div {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
form.account label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
form.account button {
|
||||
width: 100%;
|
||||
background: var(--accent-shade-0);
|
||||
}
|
||||
|
||||
form.account a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
color: var(--text-shade-1);
|
||||
text-align: right;
|
||||
margin: 6px 0;
|
||||
width: fit-content;
|
||||
}
|
||||
form.account a:hover {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
form.account a.pwdreset {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
form.account a.register {
|
||||
margin: auto;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
@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.success div {
|
||||
background: var(--green-shade-0);
|
||||
}
|
||||
|
||||
form.account.register {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
width: min(780px, 90vw);
|
||||
column-gap: 24px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
form.account.register div.h-captcha {
|
||||
grid-column: 1 / span 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
form.account.register p,
|
||||
form.account.register div.email,
|
||||
form.account.register div.buttons {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
form.account.register {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
form.account.register div.h-captcha,
|
||||
form.account.register p,
|
||||
form.account.register div.email,
|
||||
form.account.register div.buttons {
|
||||
grid-column: unset;
|
||||
}
|
||||
}
|
||||
24
public/assets/js/forgot-password.js
Normal file
24
public/assets/js/forgot-password.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const input = document.querySelector('#input');
|
||||
document.querySelector('form').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
fetch('/account/forgot-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: input.value
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(body => {
|
||||
if (body.error) {
|
||||
alert(`Error: ${body.error}. TODO: red error message thing`);
|
||||
} else {
|
||||
alert('If an account exists with the provided username/email address an email has been sent. TODO: reword this and green success');
|
||||
}
|
||||
})
|
||||
.catch(console.log);
|
||||
});
|
||||
32
public/assets/js/reset-password.js
Normal file
32
public/assets/js/reset-password.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
const config = require('../../../config.json');
|
||||
|
||||
const passwordInput = document.querySelector('#password');
|
||||
const passwordConfirmInput = document.querySelector('#password_confirm');
|
||||
const tokenInput = document.querySelector('#token');
|
||||
|
||||
document.querySelector('form').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
fetch(`${config.api_base}/v1/reset-password`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: passwordInput.value,
|
||||
password_confirm: passwordConfirmInput.value,
|
||||
token: tokenInput.value
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(body => {
|
||||
if (body.error) {
|
||||
alert(`Error: ${body.error}. TODO: red error message thing`);
|
||||
} else {
|
||||
alert('Password reset. TODO: reword this and green success');
|
||||
window.location.assign('/account/login');
|
||||
}
|
||||
})
|
||||
.catch(console.log);
|
||||
});
|
||||
|
|
@ -3,12 +3,12 @@ const PNIDSchema = require('./schema/pnid');
|
|||
const config = require('../config.json');
|
||||
|
||||
const accountServerConfig = config.database.account;
|
||||
const { uri, database, options } = accountServerConfig;
|
||||
const { connection_string, options } = accountServerConfig;
|
||||
let accountServerDBConnection;
|
||||
let PNID;
|
||||
|
||||
async function connect() {
|
||||
accountServerDBConnection = await mongoose.createConnection(`${uri}/${database}`, options);
|
||||
accountServerDBConnection = await mongoose.createConnection(connection_string, options);
|
||||
accountServerDBConnection.on('error', console.error.bind(console, 'Mongoose connection error:'));
|
||||
accountServerDBConnection.on('close', () => {
|
||||
accountServerDBConnection.removeAllListeners();
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ router.post('/register', async (request, response) => {
|
|||
}
|
||||
});
|
||||
|
||||
router.get('/logout', async(_request, response) => {
|
||||
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' });
|
||||
|
|
@ -151,6 +151,23 @@ router.get('/logout', async(_request, response) => {
|
|||
response.redirect('/');
|
||||
});
|
||||
|
||||
router.get('/forgot-password', async (request, response) => {
|
||||
response.render('account/forgot-password');
|
||||
});
|
||||
|
||||
router.post('/forgot-password', async (request, response) => {
|
||||
const apiResponse = await util.apiPostRequest('/v1/forgot-password', {}, request.body);
|
||||
response.json(apiResponse.body);
|
||||
});
|
||||
|
||||
router.get('/reset-password', async (request, response) => {
|
||||
const renderData = {
|
||||
token: decodeURIComponent(request.query.token)
|
||||
};
|
||||
|
||||
response.render('account/reset-password', renderData);
|
||||
});
|
||||
|
||||
router.get('/connect/discord', requireLoginMiddleware, async (request, response) => {
|
||||
const { pnid } = request;
|
||||
let tokens;
|
||||
|
|
@ -215,7 +232,7 @@ router.get('/remove/discord', requireLoginMiddleware, async (request, response)
|
|||
await util.removeDiscordMemberTesterRole(discordId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response.cookie('success_message', 'Discord account removed successfully', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const handlebars = require('express-handlebars');
|
|||
const morgan = require('morgan');
|
||||
const expressLocale = require('express-locale');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const Stripe = require('stripe');
|
||||
//const Stripe = require('stripe');
|
||||
const redirectMiddleware = require('./middleware/redirect');
|
||||
const renderDataMiddleware = require('./middleware/render-data');
|
||||
const database = require('./database');
|
||||
|
|
@ -15,7 +15,7 @@ const config = require('../config.json');
|
|||
|
||||
const { http: { port } } = config;
|
||||
const app = express();
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
//const stripe = new Stripe(config.stripe.secret_key);
|
||||
|
||||
logger.info('Setting up Middleware');
|
||||
app.use(morgan('dev'));
|
||||
|
|
@ -152,6 +152,7 @@ app.set('view engine', 'handlebars');
|
|||
logger.info('Starting server');
|
||||
database.connect().then(() => {
|
||||
app.listen(port, async () => {
|
||||
/*
|
||||
const events = await stripe.events.list({
|
||||
delivery_success: false // failed webhooks
|
||||
});
|
||||
|
|
@ -159,6 +160,7 @@ database.connect().then(() => {
|
|||
for (const event of events.data) {
|
||||
await util.handleStripeEvent(event);
|
||||
}
|
||||
*/
|
||||
|
||||
logger.success(`Server listening on http://localhost:${port}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -284,7 +284,6 @@ async function handleStripeEvent(event) {
|
|||
logger.error(`Error refunding subscription | ${customer.id}, ${subscription.id} | - ${error.message}`);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
|
|
|
|||
22
views/account/forgot-password.handlebars
Normal file
22
views/account/forgot-password.handlebars
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<link rel="stylesheet" href="/assets/css/forgot-password.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="account-form-wrapper">
|
||||
<form method="post" class="account">
|
||||
<h2>{{ locale.account.forgotPassword.header }}</h2>
|
||||
<p>{{ locale.account.forgotPassword.sub }}</p>
|
||||
<div>
|
||||
<label for="input">{{ locale.account.forgotPassword.input }}</label>
|
||||
<input name="input" id="input" required>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="submit">{{ locale.account.forgotPassword.submit }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/forgot-password.bundled.js"></script>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<div>
|
||||
<label for="password">{{ locale.account.loginForm.password }}</label>
|
||||
<input name="password" id="password" type="password" required>
|
||||
<a href="/account/passwordreset" class="pwdreset">{{ locale.account.loginForm.forgotPassword }}</a>
|
||||
<a href="/account/forgot-password" class="pwdreset">{{ locale.account.loginForm.forgotPassword }}</a>
|
||||
</div>
|
||||
<input name="grant_type" id="grant_type" type="hidden" value="password">
|
||||
<input name="redirect" id="redirect" type="hidden" value="{{redirect}}">
|
||||
|
|
|
|||
26
views/account/reset-password.handlebars
Normal file
26
views/account/reset-password.handlebars
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<link rel="stylesheet" href="/assets/css/reset-password.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="account-form-wrapper">
|
||||
<form method="post" class="account">
|
||||
<h2>{{ locale.account.resetPassword.header }}</h2>
|
||||
<p>{{ locale.account.resetPassword.sub }}</p>
|
||||
<div>
|
||||
<label for="password">{{ locale.account.resetPassword.password }}</label>
|
||||
<input name="password" id="password" type="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password_confirm">{{ locale.account.resetPassword.confirmPassword }}</label>
|
||||
<input name="password_confirm" id="password_confirm" type="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<input name="token" id="token" type="hidden" value="{{ token }}">
|
||||
<div class="buttons">
|
||||
<button type="submit">{{ locale.account.resetPassword.submit }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/reset-password.bundled.js"></script>
|
||||
Loading…
Reference in New Issue
Block a user