diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/routes/auth/apiKey.js | 23 | ||||
| -rw-r--r-- | src/api/routes/user/apiKey.js | 27 | ||||
| -rw-r--r-- | src/api/routes/user/changePasswordPOST.js | 40 | ||||
| -rw-r--r-- | src/api/routes/user/userGET.js | 21 | ||||
| -rw-r--r-- | src/site/pages/dashboard/account.vue | 153 |
5 files changed, 241 insertions, 23 deletions
diff --git a/src/api/routes/auth/apiKey.js b/src/api/routes/auth/apiKey.js deleted file mode 100644 index 84df2e3..0000000 --- a/src/api/routes/auth/apiKey.js +++ /dev/null @@ -1,23 +0,0 @@ -const Route = require('../../structures/Route'); - -class apiKeyGET extends Route { - constructor() { - super('/auth/apiKey', 'get'); - } - - run(req, res, user) { - return res.json({ message: 'Hai hai api works.' }); - } -} - -class apiKeyPOST extends Route { - constructor() { - super('/auth/apiKey', 'post'); - } - - run(req, res, user) { - return res.json({ message: 'Hai hai api works.' }); - } -} - -module.exports = [apiKeyGET, apiKeyPOST]; diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js new file mode 100644 index 0000000..820e28c --- /dev/null +++ b/src/api/routes/user/apiKey.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); +const randomstring = require('randomstring'); +const moment = require('moment'); + +class apiKeyPOST extends Route { + constructor() { + super('/user/apikey/change', 'post'); + } + + async run(req, res, db, user) { + const now = moment.utc().toDate(); + const apiKey = randomstring.generate(64); + await db.table('users') + .where({ id: user.id }) + .update({ + apiKey, + apiKeyEditedAt: now + }); + + return res.json({ + message: 'Successfully created new api key', + apiKey + }); + } +} + +module.exports = apiKeyPOST; diff --git a/src/api/routes/user/changePasswordPOST.js b/src/api/routes/user/changePasswordPOST.js new file mode 100644 index 0000000..d73cff3 --- /dev/null +++ b/src/api/routes/user/changePasswordPOST.js @@ -0,0 +1,40 @@ +const Route = require('../../structures/Route'); +const log = require('../../utils/Log'); +const bcrypt = require('bcrypt'); +const moment = require('moment'); + +class changePasswordPOST extends Route { + constructor() { + super('/user/password/change', 'post'); + } + + async run(req, res, db, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { password, newPassword } = req.body; + if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' }); + if (password === newPassword) return res.status(400).json({ message: 'Passwords have to be different' }); + + if (newPassword.length < 6 || newPassword.length > 64) { + return res.status(400).json({ message: 'Password must have 6-64 characters' }); + } + + let hash; + try { + hash = await bcrypt.hash(newPassword, 10); + } catch (error) { + log.error('Error generating password hash'); + log.error(error); + return res.status(401).json({ message: 'There was a problem processing your account' }); + } + + const now = moment.utc().toDate(); + await db.table('users').where('id', user.id).update({ + password: hash, + passwordEditedAt: now + }); + + return res.json({ message: 'The password was changed successfully' }); + } +} + +module.exports = changePasswordPOST; diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js new file mode 100644 index 0000000..7929aac --- /dev/null +++ b/src/api/routes/user/userGET.js @@ -0,0 +1,21 @@ +const Route = require('../../structures/Route'); + +class usersGET extends Route { + constructor() { + super('/users/me', 'get'); + } + + run(req, res, db, user) { + return res.json({ + message: 'Successfully retrieved user', + user: { + id: user.id, + username: user.username, + isAdmin: user.isAdmin, + apiKey: user.apiKey + } + }); + } +} + +module.exports = usersGET; diff --git a/src/site/pages/dashboard/account.vue b/src/site/pages/dashboard/account.vue new file mode 100644 index 0000000..e3570c7 --- /dev/null +++ b/src/site/pages/dashboard/account.vue @@ -0,0 +1,153 @@ +<style lang="scss" scoped> + @import '~/assets/styles/_colors.scss'; + section { background-color: $backgroundLight1 !important; } + section.hero div.hero-body { + align-items: baseline; + } + div.search-container { + display: flex; + justify-content: center; + } +</style> +<style lang="scss"> + @import '~/assets/styles/_colors.scss'; +</style> + + +<template> + <section class="hero is-fullheight"> + <div class="hero-body"> + <div class="container"> + <div class="columns"> + <div class="column is-narrow"> + <Sidebar /> + </div> + <div class="column"> + <h2 class="subtitle">Account settings</h2> + <hr> + + <b-field label="Username" + message="Nothing to do here" + horizontal> + <b-input v-model="user.username" + expanded + disabled /> + </b-field> + + <b-field label="Current password" + message="If you want to change your password input the current one here" + horizontal> + <b-input v-model="user.password" + type="password" + expanded /> + </b-field> + + <b-field label="New password" + message="Your new password" + horizontal> + <b-input v-model="user.newPassword" + type="password" + expanded /> + </b-field> + + <b-field label="New password again" + message="Your new password once again" + horizontal> + <b-input v-model="user.reNewPassword" + type="password" + expanded /> + </b-field> + + <div class="mb2 mt2 text-center"> + <button class="button is-primary" + @click="changePassword">Change password</button> + </div> + + <b-field label="Api key" + message="This API key lets you use the service from other apps?" + horizontal> + <b-input v-model="user.apiKey" + expanded /> + </b-field> + + <div class="mb2 mt2 text-center"> + <button class="button is-primary" + @click="promptNewAPIKey">Request new API key</button> + </div> + </div> + </div> + </div> + </div> + </section> +</template> + +<script> +import Sidebar from '~/components/sidebar/Sidebar.vue'; + +export default { + components: { + Sidebar + }, + data() { + return { + user: {} + }; + }, + computed: { + config() { + return this.$store.state.config; + } + }, + metaInfo() { + return { title: 'Account' }; + }, + mounted() { + this.$ga.page({ + page: '/dashboard/account', + title: 'Settings', + location: window.location.href + }); + this.getUserSetttings(); + }, + methods: { + async getUserSetttings() { + try { + const response = await this.axios.get(`${this.config.baseURL}/users/me`); + this.user = response.data.user; + } catch (error) { + this.$onPromiseError(error); + } + }, + async changePassword() { + if (!this.user.password || !this.user.newPassword || !this.user.reNewPassword) return; + if (this.user.newPassword !== this.user.reNewPassword) return; + + try { + const response = await this.axios.post(`${this.config.baseURL}/user/password/change`, + { + password: this.user.password, + newPassword: this.user.newPassword + }); + this.$toast.open(response.data.message); + } catch (error) { + this.$onPromiseError(error); + } + }, + promptNewAPIKey() { + this.$dialog.confirm({ + message: 'Are you sure you want to regenerate your API key?', + onConfirm: () => this.requestNewAPIKey() + }); + }, + async requestNewAPIKey() { + try { + const response = await this.axios.post(`${this.config.baseURL}/user/apikey/change`); + this.user.apiKey = response.data.apiKey; + this.$toast.open(response.data.message); + } catch (error) { + this.$onPromiseError(error); + } + } + } +}; +</script> |