Skip to content

Commit

Permalink
admin change email function
Browse files Browse the repository at this point in the history
  • Loading branch information
Tkaixiang committed Oct 29, 2021
1 parent 241db5e commit fe7f338
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 10 deletions.
4 changes: 4 additions & 0 deletions api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ const main = async () => {
instance.get('/v1/account/type', accounts.type);
instance.post('/v1/account/change/password', accounts.password);
instance.post('/v1/account/adminChangePassword', accounts.adminChangePassword);
instance.post('/v1/account/adminChangeEmail', accounts.adminChangeEmail)
instance.get('/v1/account/list', accounts.list);
instance.get('/v1/account/settings', accounts.getSettings);
instance.post('/v1/account/permissions', accounts.permissions);
Expand Down Expand Up @@ -232,6 +233,9 @@ const main = async () => {
// Email endpoints
instance.get('/v1/email/disableStates', emails.disableStates);
instance.get('/v1/email/test', emails.testConnection);
instance.post('/v1/email/adminVerify', emails.adminVerifyEmail)
instance.post('/v1/email/adminUnVerify', emails.adminUnVerifyEmail)


// Misc endpoints
instance.get('/v1/backup', misc.downloadBackup)
Expand Down
44 changes: 43 additions & 1 deletion api/controllers/Accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,48 @@ const adminChangePassword = async (req, res) => {
}
}

const adminChangeEmail = async (req, res) => {
try {
const collections = Connection.collections
if (req.locals.perms < 2) throw new Error('Permissions');
if (req.body.email == '') res.send({
success: false,
error: 'empty-email'
});
await collections.users.updateOne(
{ username: req.body.username },
{ '$set': { email: req.body.email } }
);
res.send({ success: true });
}
catch (err) {
if (err.name == 'MongoServerError') {
switch (err.code) {
case 11000:
switch (Object.keys(err.keyPattern)[0]) {
case 'email':
res.code(403);
res.send({
success: false,
error: 'email-taken'
});
return;
default:
res.send({
success: false,
error: 'validation'
});
throw new Error(err)
}
default:
throw new Error(err)
}
}
else throw new Error(err)
}

}

const list = async (req, res) => {
const collections = Connection.collections
if (req.locals.perms < 2) throw new Error('Permissions');
Expand All @@ -431,4 +473,4 @@ const permissions = async (req, res) => {
else throw new Error('NotFound');
}

module.exports = { getSettings, changeEmail, disableStates, type, create, takenUsername, takenEmail, deleteAccount, login, password, adminChangePassword, list, permissions }
module.exports = { adminChangeEmail, getSettings, changeEmail, disableStates, type, create, takenUsername, takenEmail, deleteAccount, login, password, adminChangePassword, list, permissions }
38 changes: 37 additions & 1 deletion api/controllers/Emails.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,40 @@ const disableStates = async (req, res) => {
});
}

const adminVerifyEmail = async (req, res) => {
if (req.locals.perms < 2) throw new Error('Permissions');
if (!Array.isArray(req.body.users)) throw new Error('Validation');
const collections = Connection.collections
await collections.users.updateMany({ username: { $in: req.body.users } }, { $unset: { code: 0, codeTimestamp: 0 } })
return res.send({ success: true })
}


const adminUnVerifyEmail = async (req, res) => {
if (req.locals.perms < 2) throw new Error('Permissions');
if (!Array.isArray(req.body.users)) throw new Error('Validation');
const collections = Connection.collections
await collections.users.find({ username: { $in: req.body.users } }).forEach(async (doc) => {
const code = crypto.randomBytes(32).toString('hex')
const link = NodeCacheObj.get("websiteLink") + "/verify/" + doc.username + "/" + code
await collections.users.updateOne({ username: doc.username }, { $set: { code: await argon2.hash(code), codeTimestamp: new Date() } })
const NodemailerT = NodeCacheObj.get('NodemailerT')
await NodemailerT.sendMail({
from: '"' + NodeCacheObj.get("emailSender") + '"' + ' <' + NodeCacheObj.get("emailSenderAddr") + '>', // sender address
to: doc.email,
subject: "Email Verification for Sieberrsec CTF Platform", // Subject line
text: "Dear " + doc.username + ",\n\n Please verify your email here: \n" + link + "\n\n If you did not request this email, you can safely ignore this email. You will be able to login immediately after verifying your email.", // plain text body
html: `
Dear ${doc.username},<br><br> Please verify your email by clicking <a href='${link}'>here</a>.
<br><br>
If you did not request this email, you can safely ignore this email. You will be able to login immediately after verifying your email.
`, // html body
})
})

return res.send({ success: true })
}

const verifyEmail = async (req, res) => {
if (NodeCacheObj.get("emailVerify")) {
const collections = Connection.collections
Expand Down Expand Up @@ -60,6 +94,8 @@ const verifyEmail = async (req, res) => {
})
}



const resendVerifyEmail = async (req, res) => {
if (NodeCacheObj.get("emailVerify")) {
try {
Expand Down Expand Up @@ -202,4 +238,4 @@ const resetForgottenPassword = async (req, res) => {
})
}

module.exports = { disableStates, resendVerifyEmail, testConnection, forgotPassword, resetForgottenPassword, checkPassResetLink, verifyEmail }
module.exports = { adminUnVerifyEmail, adminVerifyEmail, disableStates, resendVerifyEmail, testConnection, forgotPassword, resetForgottenPassword, checkPassResetLink, verifyEmail }
163 changes: 155 additions & 8 deletions client/src/AdminPanel/adminUsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
LockOutlined,
RedoOutlined,
SearchOutlined,
KeyOutlined
KeyOutlined,
CheckOutlined,
CloseOutlined,
} from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { Ellipsis } from 'react-spinners-css';
Expand Down Expand Up @@ -172,6 +174,70 @@ const ChangePasswordForm = (props) => {
);
}

const ChangeEmailForm = (props) => {
const [form] = Form.useForm();

return (
<Form
form={form}
onFinish={(values) => {
fetch(window.ipAddress + "/v1/account/adminChangeEmail", {
method: 'post',
headers: { 'Content-Type': 'application/json', "Authorization": window.IRSCTFToken },
body: JSON.stringify({
"username": props.username,
"email": values.email,
})
}).then((results) => {
return results.json(); //return data in JSON (since its JSON data)
}).then((data) => {
if (data.success === true) {
message.success({ content: "Email changed successfully." })
form.resetFields()
props.setState({ emailChangeModal: false })
props.fillTableData()
}
else if (data.error === "email-taken") {
message.error("Email is already in use. Please try another email.")
}
else {
message.error({ content: "Oops. Unknown error." })
}

}).catch((error) => {
console.log(error)
message.error({ content: "Oops. There was an issue connecting with the server" });
})
}}
style={{ display: "flex", flexDirection: "column", justifyContent: "center", width: "100%" }}
>
<h3>New Email</h3>
<Form.Item
name="email"
rules={[
{
required: true,
message: 'Please input the new email',
},
{
type: 'email',
message: 'Please enter a valid email'
}
]}
hasFeedback
>

<Input allowClear prefix={<MailOutlined />} placeholder="Enter a new email" />
</Form.Item>

<Form.Item>
<Button style={{ marginRight: "1.5vw" }} onClick={() => { props.setState({ emailChangeModal: false }) }}>Cancel</Button>
<Button type="primary" htmlType="submit" icon={<MailOutlined />}>Change Email</Button>
</Form.Item>
</Form>
);
}


class AdminUsers extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -202,7 +268,8 @@ class AdminUsers extends React.Component {
teamMaxSize: 3,
forgotPass: false,
emailVerify: false,
teamChangeDisable: false
teamChangeDisable: false,
emailChangeModal: false
}
}

Expand Down Expand Up @@ -426,7 +493,7 @@ class AdminUsers extends React.Component {
"uploadPath": { name: "Profile pictures upload path", loading: "uploadPathLoading", suffix: "" },
"teamMaxSize": { name: "Team mode", loading: "Maximum size of teams", suffix: "" }
}

await fetch(window.ipAddress + "/v1/adminSettings", {
method: 'post',
headers: { 'Content-Type': 'application/json', "Authorization": window.IRSCTFToken },
Expand Down Expand Up @@ -455,6 +522,60 @@ class AdminUsers extends React.Component {
this.setState({ uploadLoading: false })
}

verifyAccounts = async (close, users) => {
this.setState({ disableEditButtons: true })
await fetch(window.ipAddress + "/v1/email/adminVerify", {
method: 'post',
headers: { 'Content-Type': 'application/json', "Authorization": window.IRSCTFToken },
body: JSON.stringify({
"users": users,
})
}).then((results) => {
return results.json(); //return data in JSON (since its JSON data)
}).then((data) => {
//console.log(data)
if (data.success === true) {
message.success({ content: "User(s) [" + users.join(', ') + "] verified successfully" })
this.fillTableData()
}
else message.error("Oops. Unknown error")

}).catch((error) => {
console.log(error)
message.error({ content: "Oops. There was an issue connecting with the server" });

})
close()
this.setState({ selectedTableKeys: [] })
}

unverifyAccounts = async (close, users) => {
this.setState({ disableEditButtons: true })
await fetch(window.ipAddress + "/v1/email/adminUnVerify", {
method: 'post',
headers: { 'Content-Type': 'application/json', "Authorization": window.IRSCTFToken },
body: JSON.stringify({
"users": users,
})
}).then((results) => {
return results.json(); //return data in JSON (since its JSON data)
}).then((data) => {
//console.log(data)
if (data.success === true) {
message.success({ content: "User(s) [" + users.join(', ') + "] un-verified successfully" })
this.fillTableData()
}
else message.error("Oops. Unknown error")

}).catch((error) => {
console.log(error)
message.error({ content: "Oops. There was an issue connecting with the server" });

})
close()
this.setState({ selectedTableKeys: [] })
}


handleTableSelect = (selectedRowKeys) => {
this.setState({ selectedTableKeys: selectedRowKeys })
Expand Down Expand Up @@ -495,7 +616,7 @@ class AdminUsers extends React.Component {
onCancel={() => { this.setState({ createUserModal: false }) }}
>

<RegisterForm createAccount={this.createAccount.bind(this)} setState={this.setState.bind(this)}></RegisterForm>
<RegisterForm createAccount={this.createAccount.bind(this)} setState={this.setState.bind(this)} />
</Modal>

<Modal
Expand All @@ -505,7 +626,16 @@ class AdminUsers extends React.Component {
onCancel={() => { this.setState({ passwordResetModal: false }) }}
>

<ChangePasswordForm username={this.state.username} setState={this.setState.bind(this)}></ChangePasswordForm>
<ChangePasswordForm username={this.state.username} setState={this.setState.bind(this)} />
</Modal>
<Modal
title={"Changing Email For: " + this.state.username}
visible={this.state.emailChangeModal}
footer={null}
onCancel={() => { this.setState({ emailChangeModal: false }) }}
>

<ChangeEmailForm fillTableData={this.fillTableData.bind(this)} username={this.state.username} setState={this.setState.bind(this)} />
</Modal>


Expand All @@ -531,15 +661,25 @@ class AdminUsers extends React.Component {
onCancel: () => { },
});
}}>Delete Users</Button>
<Button type="default" disabled={this.state.disableEditButtons} style={{ marginBottom: "2vh", marginRight: "1ch", backgroundColor: "#6e6e6e" }} icon={<DeleteOutlined />} onClick={() => {
<Button type="default" disabled={this.state.disableEditButtons} style={{ marginBottom: "2vh", marginRight: "1ch", backgroundColor: "#6e6e6e" }} icon={<CheckOutlined style={{ color: "#49aa19" }} />} onClick={() => {
confirm({
confirmLoading: this.state.disableEditButtons,
title: 'Are you sure you want to verify the user(s) (' + this.state.selectedTableKeys.join(", ") + ')? This action is irreversible.',
title: 'Are you sure you want to verify the user(s) (' + this.state.selectedTableKeys.join(", ") + ')?',
icon: <ExclamationCircleOutlined />,
onOk: (close) => { this.deleteAccounts(close.bind(this), this.state.selectedTableKeys) },
onOk: (close) => { this.verifyAccounts(close.bind(this), this.state.selectedTableKeys) },
onCancel: () => { },
});
}}>Verify Users</Button>
<Button type="default" disabled={this.state.disableEditButtons} style={{ marginBottom: "2vh", marginRight: "1ch", backgroundColor: "#6e6e6e" }} icon={<CloseOutlined style={{ color: "#a61d24" }} />} onClick={() => {
confirm({
confirmLoading: this.state.disableEditButtons,
title: 'Are you sure you want to un-verify the user(s) (' + this.state.selectedTableKeys.join(", ") + ')?',
content: 'Please note that this action will send a new email per user asking them to re-verify.',
icon: <ExclamationCircleOutlined />,
onOk: (close) => { this.unverifyAccounts(close.bind(this), this.state.selectedTableKeys) },
onCancel: () => { },
});
}}>Un-Verify Users</Button>
</div>
<Table rowSelection={{ selectedRowKeys: this.state.selectedTableKeys, onChange: this.handleTableSelect.bind(this) }} style={{ overflow: "auto" }} dataSource={this.state.dataSource} locale={{
emptyText: (
Expand Down Expand Up @@ -666,6 +806,13 @@ class AdminUsers extends React.Component {
Change Password <KeyOutlined />
</span>
</Menu.Item>
<Menu.Item onClick={() => {
this.setState({ emailChangeModal: true, username: record.username })
}}>
<span>
Change Email <MailOutlined />
</span>
</Menu.Item>
</Menu>
} placement="bottomCenter">
<Button>Actions</Button>
Expand Down

0 comments on commit fe7f338

Please sign in to comment.