Account deletion working
This commit is contained in:
@@ -10,3 +10,4 @@ DB_HOSTNAME=127.0.0.1
|
|||||||
DB_DATABASE=template
|
DB_DATABASE=template
|
||||||
DB_USERNAME=template
|
DB_USERNAME=template
|
||||||
DB_PASSWORD=pikachu
|
DB_PASSWORD=pikachu
|
||||||
|
DB_TIMEZONE=Australia/Sydney
|
||||||
@@ -23,8 +23,9 @@ This should get the template working in development mode.
|
|||||||
- ~~validate email~~
|
- ~~validate email~~
|
||||||
- ~~login (with cookies)~~
|
- ~~login (with cookies)~~
|
||||||
- ~~logout (with cookies)~~
|
- ~~logout (with cookies)~~
|
||||||
- account deletion and management
|
- ~~account deletion~~
|
||||||
- annoying "This site uses cookies" message
|
- annoying "This site uses cookies" message
|
||||||
|
- CSS template?
|
||||||
- Administration Panel
|
- Administration Panel
|
||||||
- Exclusive to admin accounts
|
- Exclusive to admin accounts
|
||||||
- ban/unban accounts
|
- ban/unban accounts
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
import { useCookies } from 'react-cookie';
|
||||||
|
|
||||||
|
import DeleteAccount from '../panels/delete-account';
|
||||||
|
|
||||||
const Account = props => {
|
const Account = props => {
|
||||||
|
const [cookies, setCookie] = useCookies(['loggedin']);
|
||||||
|
|
||||||
|
//check for logged in redirect
|
||||||
|
if (!cookies['loggedin']) {
|
||||||
|
return <Redirect to='/' />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='page'>
|
<div className='page'>
|
||||||
<h1 className='middle centered'>Account</h1>
|
<h1 className='centered'>Account</h1>
|
||||||
|
<DeleteAccount />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const LogIn = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='page'>
|
<div className='page'>
|
||||||
<h1 className='middle centered'>Login</h1>
|
<h1 className='centered'>Login</h1>
|
||||||
<form className='constricted' onSubmit={
|
<form className='constricted' onSubmit={
|
||||||
evt => {
|
evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const SignUp = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='page'>
|
<div className='page'>
|
||||||
<h1 className='middle centered'>Signup</h1>
|
<h1 className='centered'>Signup</h1>
|
||||||
<form className='constricted' onSubmit={
|
<form className='constricted' onSubmit={
|
||||||
evt => {
|
evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
//DOCS: isolated the delete account button into it's own panel, so it can be easily moved as needed
|
||||||
|
const DeleteAccount = props => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
return <button onClick={() => setOpen(true)}>Delete Account</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
let passwordElement;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className='constricted' onSubmit={async evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const password = passwordElement.value;
|
||||||
|
passwordElement.value = '';
|
||||||
|
await handleSubmit(password);
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password">Password:</label>
|
||||||
|
<input type="password" name="password" ref={e => passwordElement = e} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type='submit'>Delete Account</button>
|
||||||
|
<button type='cancel' onClick={() => { passwordElement.value = ''; setOpen(false); }}>Cancel</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (password) => {
|
||||||
|
//generate a new formdata payload
|
||||||
|
let formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('password', password);
|
||||||
|
|
||||||
|
const result = await fetch('/api/accounts/deletion', { method: 'POST', body: formData });
|
||||||
|
|
||||||
|
if (!result.ok) {
|
||||||
|
alert(await result.text());
|
||||||
|
} else {
|
||||||
|
//force logout
|
||||||
|
fetch('/api/accounts/logout', { method: 'POST' })
|
||||||
|
.then(alert(await result.text()))
|
||||||
|
.then(() => window.location.reload(true)) //BUFGIX: force reload of the header element
|
||||||
|
.catch(e => console.error(e))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteAccount;
|
||||||
Generated
+46
@@ -17,6 +17,7 @@
|
|||||||
"express-formidable": "^1.2.0",
|
"express-formidable": "^1.2.0",
|
||||||
"express-session": "^1.17.1",
|
"express-session": "^1.17.1",
|
||||||
"mariadb": "^2.5.2",
|
"mariadb": "^2.5.2",
|
||||||
|
"node-cron": "^2.0.3",
|
||||||
"nodemailer": "^6.4.17",
|
"nodemailer": "^6.4.17",
|
||||||
"react-cookie": "^4.0.3",
|
"react-cookie": "^4.0.3",
|
||||||
"regenerator-runtime": "^0.13.7",
|
"regenerator-runtime": "^0.13.7",
|
||||||
@@ -5722,6 +5723,19 @@
|
|||||||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"opencollective-postinstall": "^2.0.0",
|
||||||
|
"tz-offset": "0.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||||
@@ -5986,6 +6000,14 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/opencollective-postinstall": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
|
||||||
|
"bin": {
|
||||||
|
"opencollective-postinstall": "index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/opener": {
|
"node_modules/opener": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||||
@@ -8061,6 +8083,11 @@
|
|||||||
"is-typedarray": "^1.0.0"
|
"is-typedarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tz-offset": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tz-offset/-/tz-offset-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ=="
|
||||||
|
},
|
||||||
"node_modules/uid-safe": {
|
"node_modules/uid-safe": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
@@ -14298,6 +14325,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-cron": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg==",
|
||||||
|
"requires": {
|
||||||
|
"opencollective-postinstall": "^2.0.0",
|
||||||
|
"tz-offset": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||||
@@ -14503,6 +14539,11 @@
|
|||||||
"mimic-fn": "^2.1.0"
|
"mimic-fn": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"opencollective-postinstall": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
|
||||||
|
},
|
||||||
"opener": {
|
"opener": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||||
@@ -16219,6 +16260,11 @@
|
|||||||
"is-typedarray": "^1.0.0"
|
"is-typedarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tz-offset": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tz-offset/-/tz-offset-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ=="
|
||||||
|
},
|
||||||
"uid-safe": {
|
"uid-safe": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"express-formidable": "^1.2.0",
|
"express-formidable": "^1.2.0",
|
||||||
"express-session": "^1.17.1",
|
"express-session": "^1.17.1",
|
||||||
"mariadb": "^2.5.2",
|
"mariadb": "^2.5.2",
|
||||||
|
"node-cron": "^2.0.3",
|
||||||
"nodemailer": "^6.4.17",
|
"nodemailer": "^6.4.17",
|
||||||
"react-cookie": "^4.0.3",
|
"react-cookie": "^4.0.3",
|
||||||
"regenerator-runtime": "^0.13.7",
|
"regenerator-runtime": "^0.13.7",
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
//libraries
|
||||||
|
const utils = require('util');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
var cron = require('node-cron');
|
||||||
|
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const { accounts } = require('../database/models');
|
||||||
|
|
||||||
|
//api/accounts/deletion
|
||||||
|
const route = async (req, res) => {
|
||||||
|
//make sure the account is logged in
|
||||||
|
if (req.cookies['loggedin'] !== process.env.WEB_ADDRESS) {
|
||||||
|
return res.status(401).send('invalid session status');
|
||||||
|
}
|
||||||
|
|
||||||
|
//compare the user's password
|
||||||
|
const compare = utils.promisify(bcrypt.compare);
|
||||||
|
const match = await compare(req.fields.password, req.session.account.hash);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return res.status(401).send('incorrect password');
|
||||||
|
}
|
||||||
|
|
||||||
|
//set the deletion time (2 days from now)
|
||||||
|
const interval = new Date(new Date().setDate(new Date().getDate() + 2)); //wow
|
||||||
|
await accounts.update({
|
||||||
|
deletion: interval
|
||||||
|
},
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: req.session.account.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//finally
|
||||||
|
return res.status(200).send('account will be deleted in two days - log in to cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
//actually delete the accounts
|
||||||
|
cron.schedule('0 * * * *', () => {
|
||||||
|
console.log('wiping accounts');
|
||||||
|
accounts.destroy({
|
||||||
|
where: {
|
||||||
|
deletion: {
|
||||||
|
[Op.lt]: Sequelize.fn('NOW')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = route;
|
||||||
@@ -6,5 +6,6 @@ router.post('/signup', require('./signup'));
|
|||||||
router.get('/validation', require('./validation'));
|
router.get('/validation', require('./validation'));
|
||||||
router.post('/login', require('./login'));
|
router.post('/login', require('./login'));
|
||||||
router.post('/logout', require('./logout'));
|
router.post('/logout', require('./logout'));
|
||||||
|
router.post('/deletion', require('./deletion'));
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ const route = async (req, res) => {
|
|||||||
req.session.account = account;
|
req.session.account = account;
|
||||||
res.cookie('loggedin', process.env.WEB_ADDRESS);
|
res.cookie('loggedin', process.env.WEB_ADDRESS);
|
||||||
|
|
||||||
|
//cancel deletion if any
|
||||||
|
await accounts.update({ deletion: null }, {
|
||||||
|
where: {
|
||||||
|
id: account.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//finally
|
//finally
|
||||||
res.status(200).send('login succeeded');
|
res.status(200).send('login succeeded');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const Sequelize = require('sequelize');
|
|||||||
const sequelize = new Sequelize(process.env.DB_DATABASE, process.env.DB_USERNAME, process.env.DB_PASSWORD, {
|
const sequelize = new Sequelize(process.env.DB_DATABASE, process.env.DB_USERNAME, process.env.DB_PASSWORD, {
|
||||||
host: process.env.DB_HOSTADDR,
|
host: process.env.DB_HOSTADDR,
|
||||||
dialect: 'mariadb',
|
dialect: 'mariadb',
|
||||||
|
timezone: process.env.DB_TIMEZONE,
|
||||||
logging: false
|
logging: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user