Skip to content

Commit

Permalink
Merge pull request #282 from memphisdj/feature-permissions
Browse files Browse the repository at this point in the history
Feature - User Permissions Management
  • Loading branch information
KodeStar authored Apr 21, 2021
2 parents 8448af7 + f1eacb4 commit 30dc703
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 38 deletions.
5 changes: 1 addition & 4 deletions models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ class User extends Model {
email: DataTypes.STRING,
avatar: DataTypes.STRING,
password: {
type: DataTypes.STRING,
set(val) {
this.setDataValue('password', bcrypt.hashSync(val, 10))
}
type: DataTypes.STRING
},
settings: {
type: DataTypes.TEXT,
Expand Down
22 changes: 18 additions & 4 deletions routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,26 @@ router.post(
}
})

if (req.body.username && req.body.password) {
if (!user || !user.verifyPassword(req.body.password)) {
if (!user) {
return res.status(403).json({
status: 'error',
result: 'Invalid username or password!'
})
}

if (user.password !== null) {
if (req.body.password === null || req.body.password === '' || !req.body.password) {
return res.status(403).json({
status: 'error',
result: 'invalid_user'
result: 'Invalid username or password!'
})
} else {
if (!user.verifyPassword(req.body.password)) {
return res.status(403).json({
status: 'error',
result: 'Invalid username or password!'
})
}
}
}

Expand Down Expand Up @@ -89,7 +103,7 @@ router.post(
)

/**
* Login endpoint
* Logout endpoint
*/
router.get(
'/logout',
Expand Down
6 changes: 3 additions & 3 deletions routes/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ router.get(
router.post(
'/',
errorHandler(async (req, res, next) => {
if (!req.user) {
if (!req.user || req.user.level === User.READ_ONLY) {
return res.status(401).json({
status: 'error',
result: 'unauthorized'
Expand Down Expand Up @@ -121,7 +121,7 @@ router.post(
router.put(
'/:id',
errorHandler(async (req, res, next) => {
if (!req.user) {
if (!req.user || req.user.level === User.READ_ONLY) {
return res.status(401).json({
status: 'error',
result: 'unauthorized'
Expand Down Expand Up @@ -203,7 +203,7 @@ router.put(
router.delete(
'/:id',
errorHandler(async (req, res, next) => {
if (!req.user) {
if (!req.user || req.user.level === User.READ_ONLY) {
return res.status(401).json({
status: 'error',
result: 'unauthorized'
Expand Down
45 changes: 30 additions & 15 deletions routes/users.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const express = require('express')
const router = express.Router()
const { User, Setting } = require('../models/index')
const bcrypt = require('bcrypt')
const _ = require('lodash')
const Speakeasy = require('speakeasy')
const QRCode = require('qrcode')
Expand Down Expand Up @@ -71,7 +72,7 @@ router.post(
result: 'unauthorized'
})
}

//Only admins can create users
if (usersCount > 0 && req.user.level !== User.ADMIN) {
delete req.body.level
}
Expand All @@ -88,8 +89,18 @@ router.post(
result: 'username_exists'
})
}
let pass = null
if (req.body.password) {
pass = bcrypt.hashSync(req.body.password, 10)
}

const user = await User.create(req.body)
const user = await User.create({
username: req.body.username,
password: pass,
email: req.body.email,
level: req.body.level,
settings: req.body.settings
})

return res.json({
status: 'ok',
Expand Down Expand Up @@ -117,7 +128,7 @@ router.put(
}

// only admins can edit other users
if (req.user.id !== req.params.id && req.user.level !== User.ADMIN) {
if (req.user.id.toString() !== req.params.id && req.user.level !== User.ADMIN) {
return res.status(401).json({
status: 'error',
result: 'unauthorized'
Expand All @@ -130,15 +141,19 @@ router.put(
}
})

if (req.body.currentPassword) {
if (!user.verifyPassword(req.body.currentPassword)) {
return res.status(400).json({
status: 'error',
result: 'incorrect_password'
if (req.body.updatePass) {
if (req.body.password) {
let pass = bcrypt.hashSync(req.body.password, 10)
user.update({
password: pass
})
} else {
user.update({
password: null
})
}
} else {
// If we didn't pass up the current password, don't submit a new password
delete req.body.password
} else if (!req.body.updatePass) {
delete req.body.password
}

Expand All @@ -158,7 +173,6 @@ router.put(
label: user.username
})
const qrcode = await QRCode.toDataURL(url, { scale: 6 })

user.update({
totpSecret: secret.base32
})
Expand All @@ -177,7 +191,9 @@ router.put(
window: 0
})
) {
user.update({ multifactorEnabled: true })
user.update({
multifactorEnabled: true
})

return res.json({
status: 'ok'
Expand All @@ -196,7 +212,6 @@ router.put(

return res.json({ status: 'ok' })
}

await user.update(req.body)

return res.json({
Expand All @@ -219,8 +234,8 @@ router.put(
})
}

// only admins can edit other users
if (req.user.id !== req.params.id && req.user.level !== User.ADMIN) {
// users can only change their avatar
if (req.user.id.toString() !== req.params.id && req.user.level !== User.ADMIN) {
return res.status(401).json({
status: 'error',
result: 'unauthorized'
Expand Down
28 changes: 22 additions & 6 deletions src/components/EditUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,19 @@

<q-input outlined v-model="email" :label="this.$t('email')"></q-input>

<q-input v-model="password" :label="this.$t('password')" outlined :type="isPwd ? 'password' : 'text'">
<q-input :disable="!updatePass" v-model="password" :label="this.$t('password')" outlined :type="isPwd ? 'password' : 'text'">
<template v-slot:append>
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
</template>
</q-input>
<div class="q-mb-md">
<q-checkbox v-model="updatePass" :label="user.id ? $t('update_pass') : this.$t('create_pass')" @click="updatePass = !updatePass">
<q-tooltip content-style="font-size: 15px" max-width="360px" :offset="[10, 10]">{{ user.id ? $t('update_pass_tooltip') : $t('create_pass_tooltip') }}</q-tooltip>
</q-checkbox>
</div>
<q-select v-if="currentUser.level === 0" outlined v-model="level" :options="permissions" :label="this.$t('user_permissions')" emit-value map-options></q-select>

<!--<q-select
outlined
<!--outlined
:options="languages"
:label="this.$t('select_language')"
option-value="value"
Expand Down Expand Up @@ -116,12 +121,18 @@ export default {
components: {},
computed: {
currentUser() {
return this.$store.state.app.user
},
user() {
return this.$store.state.users.edit
},
create() {
return this.$store.state.users.create
},
permissions() {
return this.$store.state.users.permissions
},
/* languages() {
return this.$store.state.app.languages
}, */
Expand All @@ -137,8 +148,10 @@ export default {
email: null,
username: null,
password: '',
level: null,
totp: null,
isPwd: true,
updatePass: false,
actions: false,
multifactorEnabled: false,
qrcode: null,
Expand All @@ -147,7 +160,7 @@ export default {
changeavatar: false,
settingtab: 'general',
settingsLanguage: null,
urlvatar: '',
urlavatar: '',
mfacode: null,
mfalinks: {
link1: '<a href="https://support.google.com/accounts/answer/1066447">Google Authenticator</a>',
Expand All @@ -163,6 +176,7 @@ export default {
this.email = newdata.email
this.username = newdata.username
this.password = newdata.password
this.level = newdata.level
// this.settingsLanguage = newdata.settings.language
this.multifactorEnabled = newdata.multifactorEnabled
},
Expand Down Expand Up @@ -244,8 +258,9 @@ export default {
}
const media = new FormData()
if (this.email !== null) formData.email = this.email
if (this.password !== '') formData.password = this.password
if (this.updatePass !== false) formData.password = this.password
if (this.level !== null) formData.level = this.level
formData.updatePass = this.updatePass
// Settings
formData.settings = {}
if (this.settingsLanguage !== null) formData.settings.language = this.settingsLanguage.value
Expand All @@ -263,6 +278,7 @@ export default {
await this.$store.dispatch('users/save', data)
await this.$store.dispatch('users/getUsers')
await this.$store.dispatch('app/status')
await this.$store.dispatch('users/clear')
this.$store.commit('users/edit', this.$store.state.app.user)
this.$store.commit('users/create', false)
this.$q.notify({
Expand Down
2 changes: 1 addition & 1 deletion src/components/MenuList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<q-tab-panels v-model="activetab" animated>
<q-tab-panel name="user">
<EssentialLink :title="this.$t('dashboard')" :caption="this.tiles + ' ' + this.$tc('app', this.tiles)" icon="dashboard" link="/" />
<EssentialLink :title="this.$tc('application')" :caption="this.active_tiles + ' ' + this.$tc('app', this.active_tiles)" icon="apps" link="/account" />
<EssentialLink v-if="user.level !== 2" :title="this.$tc('application')" :caption="this.active_tiles + ' ' + this.$tc('app', this.active_tiles)" icon="apps" link="/account" />
<EssentialLink :title="this.$t('settings')" icon="settings" link="/account/settings" />
</q-tab-panel>

Expand Down
7 changes: 6 additions & 1 deletion src/i18n/en-us/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,10 @@ export default {
invalid_input_url: 'Please provide a full URL including protocol, such as https://',
miscellaneous_settings: 'Miscellaneous Settings',
miscellaneous_settings_more: 'Settings related to opening tabs',
link_open: 'Links open in'
link_open: 'Links open in',
user_permissions: 'User permissions',
update_pass: 'Update password?',
update_pass_tooltip: 'If you want to update the password, tick the box and fill the desired password (For NO password, leave password blank)',
create_pass: 'Create password?',
create_pass_tooltip: 'If you want password tick the box and fill the desired password, else dont tick the box'
}
2 changes: 1 addition & 1 deletion src/layouts/User.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<q-layout v-if="user.level === 0" view="lHh Lpr lFf">
<q-layout view="lHh Lpr lFf">
<q-header class="bg-grey-1 text-grey-7" bordered>
<q-toolbar>
<q-btn flat dense round @click="leftDrawerOpen = !leftDrawerOpen" icon="menu" aria-label="Menu" />
Expand Down
3 changes: 1 addition & 2 deletions src/store/users/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ export function saveUser(context, data) {
export async function save(context, data) {
if (data.id === null) {
const user = await axios.post(process.env.BACKEND_LOCATION + 'users', data.user)
console.log(user)
let media = null
if (data.media) {
media = await axios.put(process.env.BACKEND_LOCATION + 'users/' + user.data.id + '/avatar', data.media)
media = await axios.put(process.env.BACKEND_LOCATION + 'users/' + user.data.result.id + '/avatar', data.media)
}
return {
user: user,
Expand Down
15 changes: 15 additions & 0 deletions src/store/users/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ export default function () {
return {
all: [],
create: false,
level: null,
permissions: [
{
label: 'Admin',
value: 0
},
{
label: 'Normal User',
value: 1
},
{
label: 'Read Only',
value: 2
}
],
edit: {
id: null,
avatar: null,
Expand Down
3 changes: 2 additions & 1 deletion tests/tests.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { User, Item } = require('../models/index')
const bcrypt = require('bcrypt')

beforeAll(async () => {
await User.destroy({ where: {} })
Expand All @@ -8,7 +9,7 @@ test('Creates a user', async () => {
const user = await User.create({
username: 'admin',
email: '[email protected]',
password: 'admin'
password: bcrypt.hashSync('admin', 10)
})

expect(user.username).toBe('admin')
Expand Down

0 comments on commit 30dc703

Please sign in to comment.