Skip to content

Commit 0ff8ef3

Browse files
committed
delete account & admin change account password
1 parent 002b2db commit 0ff8ef3

File tree

5 files changed

+240
-34
lines changed

5 files changed

+240
-34
lines changed

api/api.js

+48-21
Original file line numberDiff line numberDiff line change
@@ -145,29 +145,29 @@ MongoDB.MongoClient.connect('mongodb://localhost:27017', {
145145

146146
// (2) Insert Validation
147147
try {
148-
await db.command({collMod: "users", validator: validators.users})
149-
await db.command({collMod: "transactions", validator: validators.transactions})
150-
await db.command({collMod: "challs", validator: validators.challs})
148+
await db.command({ collMod: "users", validator: validators.users })
149+
await db.command({ collMod: "transactions", validator: validators.transactions })
150+
await db.command({ collMod: "challs", validator: validators.challs })
151151
console.log("Validation inserted")
152152
}
153-
catch (e) {console.error(e)}
153+
catch (e) { console.error(e) }
154154

155155
// (3) Create Indexes
156156
if ((await collections.users.indexes()).length === 1) {
157157
// Users indexes
158-
collections.users.createIndex({"username": 1}, {unique: true, name: "username"})
159-
collections.users.createIndex({"email": 1}, {unique: true, name: "email"})
158+
collections.users.createIndex({ "username": 1 }, { unique: true, name: "username" })
159+
collections.users.createIndex({ "email": 1 }, { unique: true, name: "email" })
160160
console.log("Users indexes created")
161161
}
162162
if ((await collections.challs.indexes()).length === 1) {
163163
// Challs indexes
164-
collections.challs.createIndex({"category": 1, "visibility": 1}, {name: "catvis"})
165-
collections.challs.createIndex({"name": 1}, {unique: true, name: "name"})
164+
collections.challs.createIndex({ "category": 1, "visibility": 1 }, { name: "catvis" })
165+
collections.challs.createIndex({ "name": 1 }, { unique: true, name: "name" })
166166
console.log("Challs indexes created")
167167
}
168168
if ((await collections.transactions.indexes()).length === 1) {
169169
// Transcations indexes
170-
collections.transactions.createIndex({"author" : 1, "challenge" : 1, "type" : 1}, {name: "userchall"})
170+
collections.transactions.createIndex({ "author": 1, "challenge": 1, "type": 1 }, { name: "userchall" })
171171
console.log("Transcations indexes created")
172172
}
173173

@@ -368,6 +368,8 @@ MongoDB.MongoClient.connect('mongodb://localhost:27017', {
368368
res.send({ success: true });
369369
}
370370
else {
371+
const user = await collections.users.findOne({ username: userToDelete }, { projection: { password: 1, _id: 0 } });
372+
if (!(await argon2.verify(user.password, req.body.password))) return res.send({success: false, error: "wrong-pass"})
371373
if ((await collections.users.deleteOne({ username: userToDelete.toLowerCase() })).deletedCount == 0) {
372374
res.status(400);
373375
res.send({
@@ -478,6 +480,31 @@ MongoDB.MongoClient.connect('mongodb://localhost:27017', {
478480
errors(err, res);
479481
}
480482
});
483+
app.post('/v1/account/adminChangePassword', async (req, res) => {
484+
try {
485+
if (req.headers.authorization == undefined) throw new Error('MissingToken');
486+
const username = signer.unsign(req.headers.authorization);
487+
if (await checkPermissions(username) < 2) throw new Error('Permissions');
488+
if (req.body.password == '') throw new Error('EmptyPassword');
489+
await collections.users.updateOne(
490+
{ username: req.body.username },
491+
{ '$set': { password: await argon2.hash(req.body.password) } }
492+
);
493+
res.send({ success: true });
494+
}
495+
catch (err) {
496+
switch (err.message) {
497+
case 'EmptyPassword':
498+
res.status(400);
499+
res.send({
500+
success: false,
501+
error: 'empty-password'
502+
});
503+
return;
504+
}
505+
errors(err, res);
506+
}
507+
});
481508
app.get('/v1/announcements/list/:version', async (req, res) => {
482509
try {
483510
if (req.headers.authorization == undefined) throw new Error('MissingToken');
@@ -1413,7 +1440,7 @@ MongoDB.MongoClient.connect('mongodb://localhost:27017', {
14131440
//websocket methods
14141441
wss.on('connection', (socket) => {
14151442
socket.isAlive = true
1416-
socket.on('pong', () => {socket.isAlive = true}); // check for any clients that dced without informing the server
1443+
socket.on('pong', () => { socket.isAlive = true }); // check for any clients that dced without informing the server
14171444

14181445
socket.on("message", async (msg) => {
14191446
const data = JSON.parse(msg)
@@ -1430,31 +1457,31 @@ MongoDB.MongoClient.connect('mongodb://localhost:27017', {
14301457
return socket.terminate()
14311458
}
14321459
socket.isAuthed = true
1433-
1460+
14341461
if (payload.lastChallengeID < cache.latestSolveSubmissionID) {
1435-
const challengesToBeSent = await collections.transactions.find(null, {projection: {_id: 0, author: 1, timestamp: 1, points: 1 }}).sort({$natural:-1}).limit(cache.latestSolveSubmissionID-payload.lastChallengeID).toArray();
1436-
socket.send(JSON.stringify({type: "init", data: challengesToBeSent, lastChallengeID: cache.latestSolveSubmissionID}))
1462+
const challengesToBeSent = await collections.transactions.find(null, { projection: { _id: 0, author: 1, timestamp: 1, points: 1 } }).sort({ $natural: -1 }).limit(cache.latestSolveSubmissionID - payload.lastChallengeID).toArray();
1463+
socket.send(JSON.stringify({ type: "init", data: challengesToBeSent, lastChallengeID: cache.latestSolveSubmissionID }))
14371464
}
1438-
else socket.send(JSON.stringify({type: "init", data: "up-to-date"}))
1465+
else socket.send(JSON.stringify({ type: "init", data: "up-to-date" }))
14391466
}
14401467
})
14411468
})
14421469

14431470
// check for any clients that dced without informing the server
14441471
const interval = setInterval(function ping() {
14451472
wss.clients.forEach(function each(ws) {
1446-
if (ws.isAlive === false) return ws.terminate();
1447-
1448-
ws.isAlive = false;
1449-
ws.ping();
1473+
if (ws.isAlive === false) return ws.terminate();
1474+
1475+
ws.isAlive = false;
1476+
ws.ping();
14501477
});
1451-
}, 30000);
1478+
}, 30000);
14521479

14531480
wss.on('close', function close() {
14541481
clearInterval(interval);
1455-
});
1482+
});
1483+
14561484

1457-
14581485

14591486

14601487
}).catch(err => {

client/public/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="utf-8" />
66
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
77
<script>
8-
window.production = true
8+
window.production = false
99
window.ipAddress = window.production ? "https://api.irscybersec.tk" : "http://localhost:20001"
1010

1111
const startEruda = () => {

client/src/App.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ class App extends React.Component {
318318
<Route exact path='/Scoreboard' render={(props) => <Scoreboard {...props} transition={style} />} />
319319

320320
<Route exact path='/Profile' render={(props) => <Profile {...props} transition={style} username={this.state.username} key={window.location.pathname} />} />
321-
<Route exact path='/Settings' render={(props) => <Settings {...props} transition={style} username={this.state.username} key={window.location.pathname} />} />
321+
<Route exact path='/Settings' render={(props) => <Settings {...props} transition={style} logout={this.handleLogout.bind(this)} username={this.state.username} key={window.location.pathname} />} />
322322
<Route exact path='/Profile/:user' render={(props) => <Profile {...props} transition={style} username={this.state.username} key={window.location.pathname} />} />
323323

324324

client/src/Settings.js

+86-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
2-
import { Layout, message, Avatar, Button, Form, Input, Divider, Upload } from 'antd';
2+
import { Layout, message, Avatar, Button, Form, Input, Divider, Upload, Modal } from 'antd';
33
import {
44
KeyOutlined,
55
LockOutlined,
6-
UploadOutlined
6+
UploadOutlined,
7+
DeleteOutlined
78
} from '@ant-design/icons';
89
import './App.min.css';
910

@@ -99,13 +100,71 @@ const ChangePasswordForm = (props) => {
99100
);
100101
}
101102

103+
const DeleteAccountForm = (props) => {
104+
const [form] = Form.useForm();
105+
106+
return (
107+
<Form
108+
form={form}
109+
name="changePassword"
110+
className="change-password-form"
111+
onFinish={(values) => {
112+
113+
fetch(window.ipAddress + "/v1/account/delete", {
114+
method: 'post',
115+
headers: { 'Content-Type': 'application/json', "Authorization": localStorage.getItem("IRSCTF-token") },
116+
body: JSON.stringify({
117+
"password": values.password
118+
})
119+
}).then((results) => {
120+
return results.json(); //return data in JSON (since its JSON data)
121+
}).then((data) => {
122+
if (data.success === true) {
123+
message.success({ content: "Account deleted successfully" })
124+
props.setState({deleteAccountModal: false})
125+
props.logout()
126+
form.resetFields()
127+
}
128+
else if (data.error === "wrong-password") {
129+
message.error({ content: "Password is incorrect. Please try again." })
130+
}
131+
else {
132+
message.error({ content: "Oops. Unknown error." })
133+
}
134+
135+
}).catch((error) => {
136+
console.log(error)
137+
message.error({ content: "Oops. There was an issue connecting with the server" });
138+
})
139+
}}
140+
style={{ display: "flex", flexDirection: "column", justifyContent: "center", width: "100%", marginBottom: "2vh" }}
141+
>
142+
<h4>Your account data will be <b style={{color: "#d32029"}}>deleted permanently</b>. Please ensure you really no longer want this account.</h4>
143+
<h3>Please Enter Your Password To Confirm:</h3>
144+
<Form.Item
145+
name="password"
146+
rules={[{ required: true, message: 'Please input your password', }]}>
147+
148+
<Input.Password allowClear prefix={<LockOutlined />} placeholder="Enter password." />
149+
</Form.Item>
150+
151+
152+
<Form.Item>
153+
<Button style={{ marginRight: "1.5vw" }} onClick={() => { props.setState({ deleteAccountModal: false }) }}>Cancel</Button>
154+
<Button type="primary" htmlType="submit" danger icon={<KeyOutlined />}>Delete Account</Button>
155+
</Form.Item>
156+
</Form>
157+
);
158+
}
159+
102160
class Settings extends React.Component {
103161

104162
constructor(props) {
105163
super(props);
106164
this.state = {
107165
fileList: [],
108-
disableUpload: false
166+
disableUpload: false,
167+
deleteAccountModal: false
109168
}
110169
}
111170

@@ -116,10 +175,23 @@ class Settings extends React.Component {
116175
render() {
117176
return (
118177
<Layout className="layout-style">
178+
179+
<Modal
180+
title={"Delete Account"}
181+
visible={this.state.deleteAccountModal}
182+
footer={null}
183+
onCancel={() => { this.setState({ deleteAccountModal: false }) }}
184+
confirmLoading={this.state.modalLoading}
185+
>
186+
187+
<DeleteAccountForm logout={this.props.logout.bind(this)} setState={this.setState.bind(this)} />
188+
</Modal>
189+
190+
119191
<Divider />
120192
<div style={{ display: "flex", marginRight: "5ch", alignItems: "center", justifyItems: "center" }}>
121193
<div style={{ display: "flex", flexDirection: "column", justifyContent: "initial", width: "15ch", overflow: "hidden" }}>
122-
<Avatar style={{ backgroundColor: "transparent", width: "12ch", height: "12ch" }} size='large' src={"https://api.irscybersec.tk/uploads/profile/" + this.props.username + ".webp"}/>
194+
<Avatar style={{ backgroundColor: "transparent", width: "12ch", height: "12ch" }} size='large' src={"https://api.irscybersec.tk/uploads/profile/" + this.props.username + ".webp"} />
123195
<div style={{ marginTop: "2ch" }}>
124196
<Upload
125197
fileList={this.state.fileList}
@@ -128,19 +200,19 @@ class Settings extends React.Component {
128200
action={window.ipAddress + "/v1/profile/upload"}
129201
maxCount={1}
130202
onChange={(file) => {
131-
this.setState({fileList: file.fileList})
203+
this.setState({ fileList: file.fileList })
132204
if (file.file.status === "uploading") {
133-
this.setState({disableUpload: true})
205+
this.setState({ disableUpload: true })
134206
}
135207
else if ("response" in file.file) {
136208
if (file.file.response.success) message.success("Uploaded profile picture")
137209
else {
138210
message.error("Failed to upload profile picture")
139211
if (file.file.response.error === "too-large") {
140-
message.info("Please upload a file smaller than " + file.file.response.size.toString() + "Bytes." )
212+
message.info("Please upload a file smaller than " + file.file.response.size.toString() + "Bytes.")
141213
}
142214
}
143-
this.setState({fileList: [], disableUpload: false})
215+
this.setState({ fileList: [], disableUpload: false })
144216
}
145217
}}
146218
headers={{ "Authorization": localStorage.getItem("IRSCTF-token") }}
@@ -167,6 +239,12 @@ class Settings extends React.Component {
167239
</div>
168240

169241
<Divider />
242+
243+
<div>
244+
<h3>Very Very Dangerous Button</h3>
245+
<Button danger type="primary" icon={<DeleteOutlined />} onClick={() => {this.setState({deleteAccountModal: true})}} >Delete Account</Button>
246+
<p>You will be asked to key in your password to confirm</p>
247+
</div>
170248
</Layout>
171249
)
172250
}

0 commit comments

Comments
 (0)